C++ 黑客编程揭秘与防范(第3版)
上QQ阅读APP看书,第一时间看更新

2.4.3 Ping命令的实现

有了前面的基础,就可以构造自己的ICMP数据报来构造自己的Ping命令了。首先,定义两个常量,还有计算校验和的函数,具体如下:

        struct icmp_header
        {
            unsigned char icmp_type;          // 消息类型
            unsigned char icmp_code;          // 代码
            unsigned short icmp_checksum;    // 校验和
            unsigned short icmp_id;           // 用来唯一标识此请求的ID号,通常设置为进程ID
            unsigned short icmp_sequence;    // 序列号
            unsigned long icmp_timestamp;    // 时间戳
        };

        #define ICMP_HEADER_SIZE sizeof(icmp_header)
        #define ICMP_ECHO_REQUEST 0x08
        #define ICMP_ECHO_REPLY 0x00

        // 计算校验和
        unsigned short chsum(struct icmp_header *picmp, int len)
        {
            long sum = 0;
            unsigned short *pusicmp = (unsigned short *)picmp;

            while ( len > 1 )
            {
                sum += *(pusicmp++);
                if ( sum & 0x80000000 )
                {
                    sum = (sum & 0xffff) + (sum >> 16);
                }
                len -= 2;
            }

            if ( len )
            {
                sum += (unsigned short)*(unsigned char *)pusicmp;
            }

            while ( sum >> 16 )
            {
                sum = (sum & 0xffff) + (sum >> 16);
            }

            return (unsigned short)~sum;
        }

ICMP的校验值是一个16位的无符号整型,它会将ICMP协议头不的数据进行累加,当累加有溢出的话,会将溢出的部分也进行累加。具体计算校验和的算法就不过多介绍了,如果对校验和计算的代码不了解,可以进行单步调试来进行分析。再来看一下对于ICMP结构体的填充,具体代码如下:

        BOOL MyPing(char *szDestIp)
        {
            BOOL bRet = TRUE;
            WSADATA wsaData;
            int nTimeOut = 1000;
            char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };
            icmp_header *pIcmp = (icmp_header *)szBuff;
            char icmp_data[32] = { 0 };

            WSAStartup(MAKEWORD(2, 2), &wsaData);
            // 创建原始套接字
            SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);

            // 设置接收超时
            setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));
              // 设置目的地址
              sockaddr_in dest_addr;
              dest_addr.sin_family = AF_INET;
              dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);
              dest_addr.sin_port = htons(0);

              // 构造ICMP封包
              pIcmp->icmp_type = ICMP_ECHO_REQUEST;
              pIcmp->icmp_code = 0;
              pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
              pIcmp->icmp_sequence = 0;
              pIcmp->icmp_timestamp = 0;
              pIcmp->icmp_checksum = 0;

              // 拷贝数据
              // 这里的数据可以是任意的
              // 这里使用abc是为了和系统提供的看起来一样
              memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);

              // 计算校验和
              pIcmp->icmp_checksum = chsum((struct icmp_header *)szBuff, sizeof(szBuff));

              sockaddr_in from_addr;
              char szRecvBuff[1024];
              int nLen = sizeof(from_addr);
              sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));
              recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);

              // 判断接收到的是否是自己请求的地址
              if ( lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp) )
              {
                  bRet = FALSE;
              }
              else
              {
                  struct icmp_header *pIcmp1 = (icmp_header *)(szRecvBuff + 20);
                  printf("%s\r\n", inet_ntoa(from_addr.sin_addr));
              }

              return bRet;
          }

这就是Ping命令的全部代码了。自己写一个函数调用它进行测试。

:在Windows XP以上的操作系统中运行时,比如Windows 8系统,程序可能会无法正常的运行,这是因为操作系统权限所导致的。在被编译好的程序上单击右键,在弹出的菜单上选择“以管理员身份运行”,这样程序就可以正常的执行了。