Linux下C/C++数据包嗅探(Sniffer)

我们知道当任何数据必须通过计算机网络传输时,它在发送方节点被分解成更小的单元,称为数据包。在接收方节点以原始格式重新组装。它是计算机网络上最小的通信单元。它也被称为块、段、数据报或单元。通过计算机网络捕获数据包的行为称为包嗅探。

数据包嗅探是网络安全中的重要概念,通过Wireshark、Tcpdump、Netwox、Scapy等工具捕获数据包及分析数据包。并深入了解这些工具背后的技术。

嗅探器的原理

Sniffer是利用计算机的网络接口截获目的地为其他计算机的数据报文的一种工具。

  • 硬件数据包嗅探器

硬件包嗅探器设计用于插入网络并对其进行检查。当试图查看特定网络段的流量时,硬件包嗅探器特别有用。通过在适当的位置直接插入物理网络,硬件数据包嗅探器可以确保数据包不会因过滤、路由或其他故意或无意原因而丢失。硬件包嗅探器存储收集的包或将其转发到收集器,收集器记录由硬件包嗅探器收集的数据以供进一步分析。

  • 软件包嗅探器

如今,大多数数据包嗅探器都是各种各样的软件。虽然连接到网络的任何网络接口都可以接收经过的每一位网络流量,但大多数网络接口都配置为不这样做。软件包嗅探器更改此配置,以便网络接口将所有网络流量向上传递到堆栈。对于大多数网络适配器,这种配置称为混杂模式。一旦处于混杂模式,数据包嗅探器的功能就变成了分离、重组和记录所有通过接口的软件数据包的问题,而不管它们的目的地址如何。软件包嗅探器收集流经物理网络接口的所有流量。然后根据软件的数据包嗅探要求记录并使用该流量。

嗅探器的工作环境

snifffer就是能够捕获网络报文的设备。嗅探器的正当用处在于分析网络的流量,以便找出所关心的网络中潜在的问题。例如,假设网络的某一段运行得不是很好,报文的发送比较慢,而我们又不知道问题出在什么地方,此时就可以用嗅探器来作出精确的问题判断。

捕获整个网络上的数据可能需要多个包嗅探器。因为每个收集器只能收集网络适配器接收的网络流量,所以它可能无法看到路由器或交换机另一侧的流量。在无线网络上,大多数适配器一次只能连接到一个信道。为了在多个网络段或多个无线信道上捕获数据,需要在网络的每个段上使用数据包嗅探器。

数据包的嗅探

先在另一个终端执行写好的my_ping命令,然后在另一个终端运行一个嗅探-伪造程序,当监听到此局域网上有ICMP请求报文,便立即使用伪造技术来进行一个响应,并且伪造的ICMP回复数据包与正确的ICMP回复数据包不应该有过大的区别,使得不易被察觉。

my_sniffer


/* Ethernet header */
struct sniff_ethernet {
        u_char  ether_dhost[ETHER_ADDR_LEN];    
        u_char  ether_shost[ETHER_ADDR_LEN];    
        u_short ether_type;                    
};

/* IP header */
struct sniff_ip {
        u_char  ip_vhl;                 
        u_char  ip_tos;              
        u_short ip_len;              
        u_short ip_id;                
        u_short ip_off;                
        #define IP_RF 0x8000          
        #define IP_DF 0x4000       
        #define IP_MF 0x2000           
        #define IP_OFFMASK 0x1fff     
        u_char  ip_ttl;               
        u_char  ip_p;            
        u_short ip_sum;             
        struct  in_addr ip_src,ip_dst;  
};
#define IP_HL(ip)               (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip)                (((ip)->ip_vhl) >> 4)

/* TCP header */
typedef u_int tcp_seq;

struct sniff_tcp {
        u_short th_sport;               
        u_short th_dport;              
        tcp_seq th_seq;               
        tcp_seq th_ack;                
        u_char  th_offx2;            
#define TH_OFF(th)      (((th)->th_offx2 & 0xf0) >> 4)
        u_char  th_flags;
        #define TH_FIN  0x01
        #define TH_SYN  0x02
        #define TH_RST  0x04
        #define TH_PUSH 0x08
        #define TH_ACK  0x10
        #define TH_URG  0x20
        #define TH_ECE  0x40
        #define TH_CWR  0x80
        #define TH_FLAGS        (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
        u_short th_win;         
        u_short th_sum;               
        u_short th_urp;              
};

...
int main(int argc, char **argv)
{

	...

	if (argc == 2) {
		dev = argv[1];
	}
	else if (argc > 2) {
		fprintf(stderr, "error: unrecognized command-line options\n\n");
		print_app_usage();
		exit(EXIT_FAILURE);
	}
	else {

		dev = pcap_lookupdev(errbuf);
		if (dev == NULL) {
			fprintf(stderr, "Couldn't find default device: %s\n",
			    errbuf);
			exit(EXIT_FAILURE);
		}
	}
	

	if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
		fprintf(stderr, "Couldn't get netmask for device %s: %s\n",
		    dev, errbuf);
		net = 0;
		mask = 0;
	}

	printf("Device: %s\n", dev);
	printf("Number of packets: %d\n", num_packets);
	printf("Filter expression: %s\n", filter_exp);


	handle = pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf);
	if (handle == NULL) {
		fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
		exit(EXIT_FAILURE);
	}

	if (pcap_datalink(handle) != DLT_EN10MB) {
		fprintf(stderr, "%s is not an Ethernet\n", dev);
		exit(EXIT_FAILURE);
	}

	if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
		fprintf(stderr, "Couldn't parse filter %s: %s\n",
		    filter_exp, pcap_geterr(handle));
		exit(EXIT_FAILURE);
	}

	if (pcap_setfilter(handle, &fp) == -1) {
		fprintf(stderr, "Couldn't install filter %s: %s\n",
		    filter_exp, pcap_geterr(handle));
		exit(EXIT_FAILURE);
	}

	pcap_loop(handle, num_packets, got_packet, NULL);
	
	pcap_freecode(&fp);
	pcap_close(handle);

	printf("\nCapture complete.\n");

	return 0;
}

运行结果:

ICMP ping的主要目的是测试设备之间的通信。数据作为请求从一台主机发送到另一台主机,接收主机应将该数据作为回复发送回。

ICMP协议有一个名为type的字段,表示ICMP数据包的类型。如果type字段为8,则数据包是ICMP echo(ping)请求,而如果type为0,则该数据包是一个ICMP echos(ping)应答。

my_ping

...
int main(int argc, char **argv)
{
	...

    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-4") == 0) {
            ip_version = IP_V4;
        } else if (strcmp(argv[i], "-6") == 0) {
            ip_version = IP_V6;
        } else {
            hostname = argv[i];
        }
    }

    if (hostname == NULL) {
        fprintf(stderr, "Usage: ping [-4|-6] <hostname>\n");
        goto exit_error;
    }


    if (ip_version == IP_V4 || ip_version == IP_VERSION_ANY) {
        struct addrinfo hints = {0};
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_RAW;
        hints.ai_protocol = IPPROTO_ICMP;
        error = getaddrinfo(hostname,
                            NULL,
                            &hints,
                            &addrinfo_list);
    }
    if (ip_version == IP_V6
        || (ip_version == IP_VERSION_ANY && error != 0)) {
        struct addrinfo hints = {0};
        hints.ai_family = AF_INET6;
        hints.ai_socktype = SOCK_RAW;
        hints.ai_protocol = IPPROTO_ICMPV6;
        error = getaddrinfo(hostname,
                            NULL,
                            &hints,
                            &addrinfo_list);
    }
    if (error != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
        goto exit_error;
    }

    for (addrinfo = addrinfo_list;
        addrinfo != NULL;
        addrinfo = addrinfo->ai_next) {
        sockfd = socket(addrinfo->ai_family,
                        addrinfo->ai_socktype,
                        addrinfo->ai_protocol);
        if (sockfd >= 0) {
            break;
        }
    }

    if ((int)sockfd < 0) {
        psockerror("socket");
        goto exit_error;
    }

    memcpy(&addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
    dst_addr_len = (socklen_t)addrinfo->ai_addrlen;

    freeaddrinfo(addrinfo_list);
    addrinfo = NULL;
    addrinfo_list = NULL;


    if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
        psockerror("fcntl");
        goto exit_error;
    }


    if (addr.ss_family == AF_INET6) {
        /*
         * 这允许我们在传入消息中接收IPv6数据包头。 
         */
        int opt_value = 1;
        error = setsockopt(sockfd,
                           IPPROTO_IPV6,
                           IPV6_RECVPKTINFO,
                           (char *)&opt_value,
                           sizeof(opt_value));
        if (error != 0) {
            psockerror("setsockopt");
            goto exit_error;
        }
    }

    /*
     * 将目标IP地址转换为字符串。
     */
    inet_ntop(addr.ss_family,
              addr.ss_family == AF_INET6
                  ? (void *)&((struct sockaddr_in6 *)&addr)->sin6_addr
                  : (void *)&((struct sockaddr_in *)&addr)->sin_addr,
              addr_str,
              sizeof(addr_str));

    printf("PING %s (%s)\n", hostname, addr_str);

    for (seq = 0; ; seq++) {
        struct icmp request;

        request.icmp_type =
                addr.ss_family == AF_INET6 ? ICMP6_ECHO : ICMP_ECHO;
        request.icmp_code = 0;
        request.icmp_cksum = 0;
        request.icmp_id = htons(id);
        request.icmp_seq = htons(seq);

        if (addr.ss_family == AF_INET6) {
            struct icmp6_packet request_packet = {0};

            request_packet.ip6_hdr.src = in6addr_loopback;
            request_packet.ip6_hdr.dst =
                ((struct sockaddr_in6 *)&addr)->sin6_addr;
            request_packet.ip6_hdr.plen = htons((uint16_t)ICMP_HEADER_LENGTH);
            request_packet.ip6_hdr.nxt = IPPROTO_ICMPV6;
            request_packet.icmp = request;

            request.icmp_cksum = compute_checksum((char *)&request_packet,
                                                  sizeof(request_packet));
        } else {
            request.icmp_cksum = compute_checksum((char *)&request,
                                                  sizeof(request));
        }

        error = (int)sendto(sockfd,
                            (char *)&request,
                            sizeof(request),
                            0,
                            (struct sockaddr *)&addr,
                            (int)dst_addr_len);
        if (error < 0) {
            psockerror("sendto");
            goto exit_error;
        }

        start_time = utime();

      ...

            reply = (struct icmp *)(msg_buf + ip_hdr_len);
            reply_id = ntohs(reply->icmp_id);
            reply_seq = ntohs(reply->icmp_seq);

            /*
             * 验证这确实是一个回送回复数据包。 
             */
            if (!(addr.ss_family == AF_INET
                  && reply->icmp_type == ICMP_ECHO_REPLY)
                && !(addr.ss_family == AF_INET6
                     && reply->icmp_type == ICMP6_ECHO_REPLY)) {
                continue;
            }

            /*
             * 验证ID和序列号,以确保回复与当前请求相关联。 
             */
            if (reply_id != id || reply_seq != seq) {
                continue;
            }

            reply_checksum = reply->icmp_cksum;
            reply->icmp_cksum = 0;

            /*
             * 验证校验和。 
             */
            if (addr.ss_family == AF_INET6) {
                size_t size = sizeof(struct ip6_pseudo_hdr) + msg_len;
                struct icmp6_packet *reply_packet = calloc(1, size);

                if (reply_packet == NULL) {
                    psockerror("malloc");
                    goto exit_error;
                }

                memcpy(&reply_packet->ip6_hdr.src,
                       &((struct sockaddr_in6 *)&addr)->sin6_addr,
                       sizeof(struct in6_addr));
                reply_packet->ip6_hdr.dst = msg_addr;
                reply_packet->ip6_hdr.plen = htons((uint16_t)msg_len);
                reply_packet->ip6_hdr.nxt = IPPROTO_ICMPV6;
                memcpy(&reply_packet->icmp,
                       msg_buf + ip_hdr_len,
                       msg_len - ip_hdr_len);

                checksum = compute_checksum((char *)reply_packet, size);
            } else {
                checksum = compute_checksum(msg_buf + ip_hdr_len,
                                            msg_len - ip_hdr_len);
            }

            printf("Received reply from %s: seq=%d, time=%.3f ms%s\n",
                   addr_str,
                   seq,
                   (double)delay / 1000.0,
                   reply_checksum != checksum ? " (bad checksum)" : "");
            break;
        }

...
}

If you need to add your own WeChat (c17865354792)
运行结果:

运行嗅探程序与ping程序,我们可以捕获到发送到特定子网的数据包和从特定子网上发出的数据包。

总结

包嗅探器用于捕获流量并分析那些捕获的流量。该报告基于分析的捕获流量生成。可以实现各种传输层协议,如TCP、UDP等,以便进行分析,并且可以根据使用的协议进行过滤。

Welcome to follow the WeChat official account【程序猿编码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值