专栏首页码农UP2U原始套接字打造ping命令

原始套接字打造ping命令

早期文章:

Ping 命令的构造

ping 命令依赖的不是TCP 协议,也不是UDP 协议,它依赖的是ICMP协议。ICMP是IP层的协议之一,它传递差错报文以及其他需要注意的信息。ICMP报文通常被IP层或高层协议使用。ICMP封装在IP数据报内部,如下图。

ICMP报文的格式如下图所示。

ICMP协议的类型码与代码根据不同的情况,各自取不同的值。Ping命令类型码用到了2个值,分别是0和8。而代码的取值都是0。当类型码取值为0时,代码的0值表示回显应答;当类型码取值为8时,代码的0值表示请求回显。Ping命令发送一个ICMP数据报时,类型码为8,代码为0,表示向对方主机进行请求回显;当收到对方的ICMP数据报时,类型码为0,代码为0,表示收到了对方主机的回显应答。简单来说,ping命令发出的数据中,类型是8,代码是0,如果对方有回应,那么对方回应的数据中,类型是0,代码是0。

在自己实现Ping命令时,就是去自己构造一个请求回显的ICMP数据报,然后进行发送。ICMP的数据结构定义如下:

// ICMP协议结构体定义
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;   // 时间戳
};

明白了ICMP协议的数据结构,现在用抓包工具(也可以称为协议分析工具)Wireshark来分析一下ICMP结构真实的情况,如下图所示。

在上图中,标识1的部分是对协议进行过滤设置的,在该部分输入“ICMP”可以让Wireshark只显示ICMP协议的数据记录。相应地,可以输入“TCP”、“UDP”、“HTTP”等协议进行筛选过滤。标识2的部分用于显示筛选后的ICMP记录,从这里可以明显看出源IP地址、目的IP地址和协议的类型。标识3的部分用于显示ICMP数据结构的值和附加的数据内容。最下面的部分显示了数据的原始的二进制数据,在熟练掌握协议后,查看原始的二进制数据也并不是不可能的。

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);
    DWORD dwStart = GetTickCount();
    sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));
    recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);
    DWORD dwEnd = GetTickCount();
    // 判断接收到的是否是自己请求的地址
    if ( lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp) )
    {
        bRet = FALSE;
    }
    else
    {
        struct icmp_header *pIcmp1 = (icmp_header *)(szRecvBuff + 20);
        printf("%s %d\r\n", inet_ntoa(from_addr.sin_addr), dwEnd - dwStart);
    }
    
    return bRet;
}

调用运行输出如下:

第一列是我们ping的IP地址,后面是数据包往返经过的毫秒数。

完整内容参考《C++黑客编程揭秘与防范》(第三版)一书。

本文分享自微信公众号 - 码农UP2U(gh_3c91b47a82e0),作者:码农UP2U

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • golang使用原始套接字构造UDP包

    RAW SOCKET 介绍 TCP/IP协议中,最常见的就是原始(SOCKET_RAW)、tcp(SOCKET_STREAM)、udp(SOCKET_DGRA)...

    李海彬
  • 学会使用这些常见的网络诊断工具,助力你的网络编程之路

    在网络服务端学习以及开发过程中,我们总会碰到这样或那样的问题。学会对这些问题进行诊断和分析,其实需要不断地积累经验。

    看、未来
  • 你知道TCP/IP协议如何测试吗?

    作为一个测试人员,如果你确实还没接触过网络、数据通信方面的技术,那么咱们的路还很长,至少我认为软件测试并非只停留在上层的应用,而测试的最高境界应该是对底层核心技...

    用户6367961
  • Java学习笔记_零基础系列(二)常用的DOS命令(续)

    del *.class 这个命令中的那个“.”不要特殊化,这个“.”其实就是一个普通的字母

    牛仔码农
  • Python3实现ICMP远控后门(上)

    七夜安全博客
  • ping命令的高级用法

    雨尘
  • Redis 主从复制以及主从复制原理

    在现有企业中80%公司大部分使用的是redis单机服务,在实际的场景当中单一节点的redis容易面临风险。

    芋道源码
  • Android网络收集和ping封装库

    杨充
  • Linux 命令(109)—— ping 命令

    ping(Packet Internet Groper 命令是因特网包探索器,用于测试网络连通性,是常用的网络命令之一。

    Dabelv
  • Java Socket:飞鸽传书的网络套接字

    在古代,由于通信不便利,一些聪明的人就利用鸽子会飞且飞得比较快、会辨认方向的优点,对其进行了驯化,用来进行消息的传递——也就是所谓的“飞鸽传书”。而在 Java...

    沉默王二
  • 《Redis设计与实现》读书笔记(二十五) ——Redis主从复制具体过程

    《Redis设计与实现》读书笔记(二十五) ——Redis主从复制具体过程 (原创内容,转载请注明来源,谢谢) 一、PSYNC命令执行过程 ...

    用户1327360
  • 一起来读开源项目的代码-Agar.io为例

    读开源项目的代码可以分为三层: 1,弄清代码创作者目的,初衷,分析架构,框架 2,分析代码的接口分析代码的框架组织 3,根据功能模块,学习代码细节

    rectinajh
  • 详解php命令注入攻击

    命令注入攻击(Command Injection),是指黑客通过利用HTML代码输入机制缺陷(例如缺乏有效验证限制的表格域)来改变网页的动态生成的内容。从而可以...

    砸漏
  • 详解php命令注入攻击

    命令注入攻击(Command Injection),是指黑客通过利用HTML代码输入机制缺陷(例如缺乏有效验证限制的表格域)来改变网页的动态生成的内容。从而可以...

    用户8832582
  • 进程组、会话、控制终端概念,如何创建守护进程?

    守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。

    睡魔的谎言
  • Go中原始套接字的深度实践

    原始套接字(raw socket)是一种网络套接字,允许直接发送/接收更底层的数据包而不需要任何传输层协议格式。平常我们使用较多的套接字(socket)都是基于...

    蘑菇先生
  • ICMP协议与ping命令

    (3)根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。

    用户7557625
  • 『高级篇』docker之DockerSwarm的集群环境搭建(28)

    PS:以上就完成高可用的docker swarm的集群环境,其实真心比其他的简单。

    IT架构圈
  • 02 . Ansible高级用法(运维开发篇)

    ansible是python中的一套模块,系统中的一套自动化工具,可以用作系统管理,自动化命令等任务

    常见_youmen

扫码关注云+社区

领取腾讯云代金券