网络安全学习第12篇 - 端口扫描工具的优化(实现:域名转换IP地址、主机存活测试以及延时扫描等功能)+ 多线程

请结合文档中的内容,编写一个利用多线程技术实现的端口扫描工具。(基于之前的博客网络安全12篇)

实验文件与参考文档.rar


相关知识:

端口扫描背景及意义

 

网络中每台计算机犹如一座城堡,这些城堡中,有些是对外完全开放的,有些却是大门紧闭的。入侵者们是如何找到,并打开它们的城门呢?这些城门究竟通向何处? 在网络中,把这些城堡的“城门”称之为计算机的“端口”。端口扫描是入侵者搜索信息的几种常用方法之一,也正是这一种方法最容易暴露入侵者的身份和意图。一般说来,扫描端口有以下目的: 判断目标主机上开放了哪些服务 判断目标主机的操作系统 如果入侵者掌握了目标主机开放了哪些服务,运行何种操作系统,他们就能使用相应的手段实现入侵。而如果管理员先掌握了这些端口服务的安全漏洞,就能采取有效的安全措施,防范相应的入侵。

 

端口扫描现状

计算机信息网络的发展加速了信息化时代的进程,但是随着社会网络化程度的增加,对计算机网络的依赖也越来越大,网络安全问题也日益明显。端口扫描技术是发现安全问题的重要手段之一。一个端口就是一个潜在的通信通道,也就是一个入侵通道。对目标计算机进行端口扫描,能得到许多有用的信息。扫描器通过选用远程TCP/IP不同的端口的服务,并记录目标给予的回答,通过这种方法,可以搜集到很多关于目标主机的各种有用的信息,从而发现目标机的某些内在的弱点。 

 

 

端口扫描工具(Port Scanner)指用于探测服务器主机开放端口情况的工具。常被计算机管理员用于确认安全策略,同时被攻击者用于识别目标主机上的可运作的网络服务。

 

端口扫描定义是客户端向一定范围的服务器端口发送对应请求,以此确认可使用的端口。虽然其本身并不是恶意的网络活动,但也是网络攻击者探测目标主机服务,以利用该服务的已知漏洞的重要手段。端口扫描的主要用途仍然只是确认远程机器某个服务的可用性。

扫描多个主机以获取特定的某个端口被称为端口清扫(Portsweep),以此获取特定的服务。例如,基于SQL服务的计算机蠕虫就会清扫大量主机的同一端口以在 1433 端口上建立TCP连接。 [1] 

 

TCP扫描

最简单的端口扫描工具使用操作系统原生的网络功能,且通常作为SYN扫描的替代选项。Nmap将这种模式称为连接扫描,因为使用了类似Unix系统的connect()命令。如果该端口是开放的,操作系统就能完成TCP三次握手,然后端口扫描工具会立即关闭刚建立的该连接,防止拒绝服务攻击。这种扫描模式的优势是用户无需特殊权限。但使用操作系统原生网络功能不能实现底层控制,因此这种扫描方式并不流行。并且TCP扫描很容易被发现,尤其作为端口清扫的手段:这些服务会记录发送者的IP地址,入侵检测系统可能触发警报。 [1] 

SYN扫描

SYN扫描是另一种TCP扫描。端口扫描工具不使用操作系统原生网络功能,而是自行生成、发送IP数据包,并监控其回应。这种扫描模式被称为“半开放扫描”,因为它从不建立完整的TCP连接。端口扫描工具生成一个SYN包,如果目标端口开放,则会返回SYN-ACK包。扫描端回应一个RST包,然后在握手完成前关闭连接。如果端口关闭了但未使用过滤,目标端口应该会持续返回RST包。

这种粗略的网络利用方式有几个优点:给扫描工具全权控制数据包发送和等待回应时长的权力,允许更详细的回应分析。关于哪一种对目标主机的扫描方式更不具备入侵性存在一些争议,但SYN扫描的优势是从不会建立完整的连接。然而,RST包可能导致网络堵塞,尤其是一些简单如打印机之类的网络设备。 [1] 

UDP扫描

UDP扫描也是可能的,尽管存在一些技术挑战。 UDP是无连接协议,因此没有等同于TCP SYN的数据包。但是,如果将UDP数据包发送到未打开的端口,目标系统将响应ICMP端口不可达的消息。大多数UDP端口扫描器都使用这种扫描方法,并使用缺少响应来推断端口是否打开。但是,如果端口被防火墙阻止,则此方法将错误地报告端口已打开。如果端口不可达消息被阻塞,则所有端口将显示为打开。这种方法也受到ICMP速率限制的影响。

另一种方法是发送特定于应用程序的UDP数据包,希望生成应用层响应。例如,如果DNS服务器存在,向端口53发送DNS查询将导致响应。这种方法在识别开放端口方面更加可靠。然而,它仅限于应用程序特定的探测包可用时的端口扫描。一些工具(例如,NMAP)通常具有少于20个UDP服务的探针,而一些商业工具(例如,NESUS)有多达70个。在某些情况下,服务能在端口上被侦听,但被配置为不响应特定的探测包。

 

-----------------------------------------------------------------------------

通过上次实验可以发现上次的程序主要有3个不足之处

①不支持输入域名直接扫描:IP地址不是特别好记忆,我们一般记住的都是这个网址的域名,而需要通过查询或者通过cmd里的ping才能获得IP地址,这非常不方便。

②connect()函数默认是阻塞,扫描端口需要一定的时间,尤其是不开放的端口,对于不在线的主机这种扫描就变得没有意义了,而且还需要耗费大量时间,因此在扫描前判断主机是否在线可以避免程序做一些无用功。

③利用connect进行端口扫描时,当连接一个开放的端口时需要的时间约为0.05s,而不开放的端口就大约会消耗21s,也就是说扫描一个不开放的端口时几乎会卡死21秒。给 connect()函数设定一个超时时间可以避免默认的多次重发,从而节约时间,提升扫描速度

对于此次端口扫描工具的优化可以了解以下知识:

要点1:域名转为IP地址函数

要点2:主机存活测试

主机存活测试即判断主机是否在线,即测试是否可以ping通。

要点3:非阻塞与延时扫描

根据实验可以发现这里涉及一个结构两个函数:

FD_SET

这是一个数组的宏定义,实际上是一long类型的数组每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。

其中系统提供的主要的对其操作的函数:

int ioctlsocket( int s, long cmd, u_long * argp);

s:一个标识套接口的描述字。

cmd:对套接口s的操作命令。

argp:指向cmd命令所带参数的指针。

int select( int nfds, fd_set FAR* readfds,fd_set * writefds, fd_set * exceptfds,const struct timeval * timeout);

nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,在Windows中这个参数的值无所谓,可以设置不正确。

readfds:(可选)指针,指向一组等待可读性检查的套接口。

writefds:(可选)指针,指向一组等待可写性检查的套接口。

exceptfds:(可选)指针,指向一组等待错误检查的套接口。

timeout:select()最多等待时间,对阻塞操作则为NULL。

注:使用非阻塞 connect 需要注意的问题有

1.很可能调用 connect时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。

2.Posix定义了两条与select和非阻塞connect相关的规定

A.连接成功建立时,socket描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)

B.连接建立失败时,socket描述字既可读又可写。(由于有未决的错误,从而可读又可写)

 

多线程

 

·  情况:开2000个线程,扫描主机上开启的端口,扫描时间40秒左右。

·  瓶颈:不管开5000还是更多,都不能大幅加快扫描时间。

·  瓶颈解决方法:可以使用最常被开放的1000个端口列表进行扫描,网上应该有,社会学+编程。

·  注意:socket是宝贵的系统资源,不用要关闭;多线程中临界区资源要加锁。

参考:https://www.cnblogs.com/woniu-felix/p/10826157.html


// PortScanf.cpp : 定义控制台应用程序的入口点。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <WinSock2.h>
#include <cstdio>
#include <iostream>
#include <ws2tcpip.h> 
#include <stdio.h>

#pragma comment(lib, "Ws2_32")
#pragma comment(lib, "Ws2_32.lib") 

using namespace std;

//获取超时时间
unsigned long nTimeout;

//#include <winsock2.h>
//#pragma comment(lib, "WS2_32")



//线程个数
#define THREADCOUNT 2000
DWORD WINAPI ThreadProc(LPVOID lpParameter);

char IP[32] = "";

//端口号
int PortNum = 0;
//临界区变量
CRITICAL_SECTION cs;
//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	//创建套接字
	//SOCKET TryConnect;

	SOCKET s;

	WSADATA wsa;
	//SOCKET s;
	struct sockaddr_in server;

	int CurrPort;    //当前端口
	//int ret;

	WSAStartup(MAKEWORD(2, 2), &wsa);    //使用winsock函数之前,必须用WSAStartup函数来装入并初始化动态连接库

	server.sin_family = AF_INET;    //指定地址格式,在winsock中只能使用AF_INET
	server.sin_addr.s_addr = inet_addr(IP); //指定被扫描的IP地址

	while (1)
	{
		if (PortNum > 65535)
		{
			break;
		}
		//进入临界区
		EnterCriticalSection(&cs);
		int tmpport = PortNum;
		PortNum++;
		//DWORD threadID=GetCurrentThreadId();
		//printf("线程%d正在检测端口%d\n",threadID,PortNum);//所有使用临界区资源的代码都要加锁
		//离开临界区
		LeaveCriticalSection(&cs);

		WSADATA wsa;
		//SOCKET s;
		struct sockaddr_in server;

		//int CurrPort;    //当前端口
		//int ret;

		WSAStartup(MAKEWORD(2, 2), &wsa);    //使用winsock函数之前,必须用WSAStartup函数来装入并初始化动态连接库

		server.sin_family = AF_INET;    //指定地址格式,在winsock中只能使用AF_INET
		server.sin_addr.s_addr = inet_addr(IP); //指定被扫描的IP地址

		SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		server.sin_port = htons(tmpport); //指定被扫描IP地址的端口号

		TIMEVAL TimeOut;
		FD_SET mask;
		unsigned long mode = 1; //ipctlsocket函数的最后一个参数

		//设置超时毫秒数
		TimeOut.tv_sec = 0;
		TimeOut.tv_usec = nTimeout;
		FD_ZERO(&mask);
		FD_SET(s, &mask);

		//设置为非阻塞模式
		ioctlsocket(s, FIONBIO, &mode);

		connect(s, (struct sockaddr *)&server, sizeof(server)); //连接
		int ret = select(0, NULL, &mask, NULL, &TimeOut);

		if (ret != 0 && ret != -1) //判断连接是否成功
		{
			printf("%s:%d Success O(∩_∩)O~~\n", IP, tmpport);
			closesocket(s);
		}
		else {
			//printf("%s:%d Failed\n", Ip, CurrPort);
		}

		closesocket(s);//防止开启太多socket连接,导致后面socket分配无效
	}
	return 0;
}

typedef struct _IPHeader
{
	unsigned char      iphVerLen;
	unsigned char      ipTOS;
	unsigned short     ipLength;
	unsigned short     ipID;
	unsigned short     ipFlags;
	unsigned char     ipTTL;
	unsigned char     ipProtocol;
	USHORT    ipChecksum;
	ULONG     ipSource;
	ULONG     ipDestination;
} IPHeader, *PIPHeader;
typedef struct icmp_hdr
{
	unsigned char   icmp_type;
	unsigned char   icmp_code;
	unsigned short  icmp_checksum;
	unsigned short  icmp_id;
	unsigned short  icmp_sequence;
	unsigned long   icmp_timestamp;
} ICMP_HDR, *PICMP_HDR;

typedef struct _EchoRequest {
	ICMP_HDR icmphdr;
	char cData[32];
}ECHOREQUEST, *PECHOREQUEST;

#define REQ_DATASIZE 32
typedef struct _EchoReply {
	IPHeader iphdr;
	ECHOREQUEST echoRequest;
}ECHOREPLAY, *PECHOREPLAY;

USHORT checksum(USHORT* buff, int size)
{
	u_long cksum = 0;
	while (size > 1)
	{
		cksum = cksum + *buff;
		buff = buff + 1;
		size = size - sizeof(USHORT);
	}
	if (size == 1)
	{
		USHORT u = 0;
		u = (USHORT)(*(UCHAR*)buff);
		cksum = cksum + u;
	}

	cksum = (cksum >> 16) + (cksum & 0x0000ffff);
	cksum = cksum + (cksum >> 16);
	u_short  answer = (u_short)(~cksum);
	return (answer);
}

//我的ping程序
BOOL MyPing(char *szDestIp) 
{
	WSADATA wsaData;
	WORD version = MAKEWORD(2, 2);
	int ret = WSAStartup(version, &wsaData);
	if (ret != 0) {
		printf(" 加载Winsock库错误! \n");
		return 0;
	}
	/*char szDestIp[100];
	printf("输入所要连接的外网地址:\n");
	scanf("%s", szDestIp);*/


	SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

	int nTimeOut = 1000;

	// 设置接收超时
	setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));

	SOCKADDR_IN dest;
	dest.sin_family = AF_INET;
	dest.sin_port = htons(0);
	dest.sin_addr.S_un.S_addr = inet_addr(szDestIp);
	ECHOREQUEST echoReq;

	echoReq.icmphdr.icmp_type = 8;
	echoReq.icmphdr.icmp_code = 0;
	echoReq.icmphdr.icmp_id = (USHORT)GetCurrentProcessId();
	echoReq.icmphdr.icmp_checksum = 0;
	echoReq.icmphdr.icmp_sequence = 0;
	memset(&echoReq.cData, 'E', 32);
	USHORT   nSeq = 0;     SOCKADDR_IN from;
	int nLen = sizeof(from);


	int fail = 0;
	int success = 0;

	int i = 0;
	int a[4];

	int isFail = 0;

	int timeQ;
	while (TRUE) {
		printf("正在循环\n");
		static int nCount = 0;   int nRet;
		if (nCount++ == 4)   break;
		echoReq.icmphdr.icmp_checksum = 0;
		echoReq.icmphdr.icmp_timestamp = GetTickCount();
		echoReq.icmphdr.icmp_sequence = nSeq++;
		echoReq.icmphdr.icmp_checksum = checksum((USHORT*)&echoReq, sizeof(echoReq));
		nRet = sendto(sRaw, (char*)&echoReq, sizeof(echoReq), 0, (SOCKADDR *)&dest, sizeof(dest));
		if (nRet == SOCKET_ERROR) {

			printf(" sendto() failed: %d \n", WSAGetLastError());
			//system("pause");
			return false;

		}

		ECHOREPLAY echoReply;
		nRet = recvfrom(sRaw, (char*)&echoReply, sizeof(ECHOREPLAY), 0, (sockaddr*)&from, &nLen);
		if (nRet == SOCKET_ERROR) {
			if (WSAGetLastError() == WSAETIMEDOUT) {
				printf(" timed out\n");
				printf("时间超时\n");
				fail++;
				continue;
			}
			printf(" recvfrom() failed: %d\n", WSAGetLastError());

			isFail = 1;

			printf("来自172.16.4.42的回复:无法访问目标主机\n");


			success++;
			continue;
		}
		if (nRet < sizeof(ECHOREPLAY)) {
			printf(" Too few bytes from %s \n", inet_ntoa(from.sin_addr));
			return false;
		}

		if (echoReply.echoRequest.icmphdr.icmp_type != 0) {
			printf(" nonecho type %d recvd \n", echoReply.echoRequest.icmphdr.icmp_type);
			//system("pause");
			return false;
		}

		if (echoReply.echoRequest.icmphdr.icmp_id != GetCurrentProcessId()) {
			printf(" someone else's packet! \n");
			//system("pause");
			return false;
		}
		printf(" %d bytes Reply from %s: \n", nRet, inet_ntoa(from.sin_addr));


		printf(" icmp_seq = %d. ", echoReply.echoRequest.icmphdr.icmp_sequence);
		int nTick = GetTickCount();
		success++;
		printf(" time: %d ms", nTick - echoReply.echoRequest.icmphdr.icmp_timestamp);

		a[i] = nTick - echoReply.echoRequest.icmphdr.icmp_timestamp;
		i++;

		printf(" TTL= %d ", echoReply.iphdr.ipTTL);
		//printf(echoReply.echoRequest.cData);
		printf(" \n");
		Sleep(1000);
	}

	printf("%s 的ping的统计信息:\n", szDestIp);
	printf("数据包:已发送 = %d,已接收 = %d,丢失 = %d\n", success, success, fail);
	if (isFail != 1) {

		printf("往返程的估计时间:(以毫秒记)\n");
		int timeC = a[0];
		int timeD = a[0];
		int timeA = a[0];
		int j;

		for (j = 1; j < 4; j++) {

			if (timeC < a[j]) {

				timeC = a[j];
			}
			if (timeD > a[j]) {

				timeD = a[j];

			}

			timeA = timeA + a[j];

		}
		timeA = timeA / 4;
		printf("最短 = %d 最长 = %d 平均 = %d\n", timeD, timeC, timeA);
	}
	else {

	}
	printf("跳出循环!\n");
	if (fail == 4)
	{
		return false;
	}
	closesocket(sRaw);
	WSACleanup();
	//system("pause");
	return true;
}

//扫描端口
int scant(char *Ip, int StartPort, int EndPort)
{
	

	WSADATA wsa;
	//SOCKET s;
	struct sockaddr_in server;

	int CurrPort;    //当前端口
	//int ret;

	WSAStartup(MAKEWORD(2, 2), &wsa);    //使用winsock函数之前,必须用WSAStartup函数来装入并初始化动态连接库

	server.sin_family = AF_INET;    //指定地址格式,在winsock中只能使用AF_INET
	server.sin_addr.s_addr = inet_addr(Ip); //指定被扫描的IP地址

	cout << "设置超时时间:" << endl;
	cout << ">>";
	//获取超时时间
	unsigned long nTimeout;
	cin >> nTimeout;

	//逐个连接从开始端口到结束端口
	for (CurrPort = StartPort; CurrPort <= EndPort; CurrPort++)
	{
		SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		server.sin_port = htons(CurrPort); //指定被扫描IP地址的端口号

		TIMEVAL TimeOut;
		FD_SET mask;
		unsigned long mode = 1; //ipctlsocket函数的最后一个参数

		//设置超时毫秒数
		TimeOut.tv_sec = 0;
		TimeOut.tv_usec = nTimeout;
		FD_ZERO(&mask);
		FD_SET(s, &mask);
		
		//设置为非阻塞模式
		ioctlsocket(s, FIONBIO, &mode);

		connect(s, (struct sockaddr *)&server, sizeof(server)); //连接
		int ret = select(0, NULL, &mask, NULL, &TimeOut);

		if (ret != 0 && ret != -1) //判断连接是否成功
		{
			printf("%s:%d Success O(∩_∩)O~~\n", Ip, CurrPort);
			closesocket(s);
		}
		else {
			//printf("%s:%d Failed\n", Ip, CurrPort);
		}
	}
	//printf("Cost time:%f second\n", CostTime); //输出扫描过程中耗费的时间
	//closesocket(server);
	WSACleanup();    //释放动态连接库并释放被创建的套接字
	return 1;
}


//扫描端口
int scant2(char *Ip, int StartPort, int EndPort)
{


	WSADATA wsa;
	//SOCKET s;
	struct sockaddr_in server;

	int CurrPort;    //当前端口
	//int ret;

	WSAStartup(MAKEWORD(2, 2), &wsa);    //使用winsock函数之前,必须用WSAStartup函数来装入并初始化动态连接库

	server.sin_family = AF_INET;    //指定地址格式,在winsock中只能使用AF_INET
	server.sin_addr.s_addr = inet_addr(Ip); //指定被扫描的IP地址

	cout << "设置超时时间:" << endl;
	cout << ">>";
	//获取超时时间
	unsigned long nTimeout;
	cin >> nTimeout;

	//逐个连接从开始端口到结束端口
	for (CurrPort = StartPort; CurrPort <= EndPort; CurrPort++)
	{
		SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		server.sin_port = htons(CurrPort); //指定被扫描IP地址的端口号

		TIMEVAL TimeOut;
		FD_SET mask;
		unsigned long mode = 1; //ipctlsocket函数的最后一个参数

		//设置超时毫秒数
		TimeOut.tv_sec = 0;
		TimeOut.tv_usec = nTimeout;
		FD_ZERO(&mask);
		FD_SET(s, &mask);

		//设置为非阻塞模式
		ioctlsocket(s, FIONBIO, &mode);

		connect(s, (struct sockaddr *)&server, sizeof(server)); //连接
		int ret = select(0, NULL, &mask, NULL, &TimeOut);

		if (ret != 0 && ret != -1) //判断连接是否成功
		{
			printf("%s:%d Success O(∩_∩)O~~\n", Ip, CurrPort);
			closesocket(s);
		}
		else {
			//printf("%s:%d Failed\n", Ip, CurrPort);
		}
	}
	//printf("Cost time:%f second\n", CostTime); //输出扫描过程中耗费的时间
	//closesocket(server);
	WSACleanup();    //释放动态连接库并释放被创建的套接字
	return 1;
}


//发送控制器
void sendController(char ip[])
{
	if (MyPing(ip) == false) //ping失败
	{
		cout << "ping失败!" << endl;
		return;
	}
	else
	{
		cout << "ping成功!" << endl;
	}

	cout << "选择:(1.单线程扫描指定端口范围   2.多线程扫描全部端口)" << endl;
	cout << ">>";
	int option;
	cin >> option;

	if (option == 1)
	{
		cout << "输入你要查询的起始端口:" << endl;
		cout << ">>";
		int startPort;
		cin >> startPort;

		cout << "输入你要查询的结束端口:" << endl;
		cout << ">>";
		int endPort;
		cin >> endPort;

		cout << "设置超时时间:" << endl;
		cout << ">>";
		cin >> nTimeout;

		scant(ip, startPort, endPort);
	}
	else if (option == 2)
	{

		cout << "设置超时时间:" << endl;
		cout << ">>";
		cin >> nTimeout;

		char tempIp[32];
		strcpy(IP, ip); //OK

		//初始化套接字
		WSADATA ws;
		::WSAStartup(MAKEWORD(2, 0), &ws);

		DWORD start = GetTickCount();
		//初始化临界区
		InitializeCriticalSection(&cs);

		//多线程扫描
		HANDLE hThread[THREADCOUNT];
		for (int i = 0; i < THREADCOUNT; i++)
		{
			hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)0, 0, NULL);
		}

		//当thread数量超过64的处理
		int tempNumThreads = THREADCOUNT;
		int tempMax = 0;
		while (tempNumThreads >= MAXIMUM_WAIT_OBJECTS)
		{
			tempNumThreads -= MAXIMUM_WAIT_OBJECTS;
			WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, &hThread[tempMax], false, INFINITE);
			tempMax += MAXIMUM_WAIT_OBJECTS;
		}
		WaitForMultipleObjects(tempNumThreads, &hThread[tempMax], false, INFINITE);

		//删除临界区
		DeleteCriticalSection(&cs);

		DWORD end = GetTickCount();
		printf("use time(s):%f\n", (end - start) / 1000.0);
	}
}

//域名装换ip函数
int hostnameToIp()
{
	//使用Ws2_32.dll的初始化 
	WORD wVersionRequested = 0;
	WSADATA wsaData = {};
	int err = 0;
 
	wVersionRequested = MAKEWORD( 2, 2 );
 
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) 
	{                       
		return -1;
	}                                  
 
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
		HIBYTE( wsaData.wVersion ) != 2 ) 
	{
 
		WSACleanup( );
		return -1; 
	}
 
	//////////////////////////
	char** pptr = NULL;
	char szHostName[256] = {};
	cout << "--------------------------------------" << endl;
	cout << "输入域名:";
	//while( cin.getline( szHostName, sizeof(szHostName) ) )
	//{
	cin.getline(szHostName, sizeof(szHostName));
	HOSTENT* pHostEntry = gethostbyname( szHostName );
	if( NULL != pHostEntry && szHostName[0] != '\0' )
	{
		//将主机的规范名输出
		cout << "主机规范名:" << pHostEntry->h_name << endl;
 
		//主机别名。可含多个
		int i = 0;
		for ( i = 1, pptr = pHostEntry->h_aliases; *pptr != NULL; ++pptr )
		{
			cout << "主机别名" << i++ << ":" << *pptr << endl;
		}
 
 
		//将主机地址列表输出,可含多个
		char szIpBuff[32] = {0};
		for ( i = 1, pptr = pHostEntry->h_addr_list; *pptr != NULL; ++pptr )
		{
			cout << "A" << endl;
			if (i == 1) //只使用一个ip
			{
				memset(szIpBuff, 0, sizeof(szIpBuff));
				//inet_ntop的返回值为NULL,则表示失败。否则返回对应的IP地址(此时szIpRet指向的是szIpBuff)
				const char* szIpRet = inet_ntop(pHostEntry->h_addrtype, *pptr, szIpBuff, sizeof(szIpBuff));
				if (szIpBuff != NULL)
				{
					cout << "解析IP地址" << i++ << ":" << szIpRet << endl;
					char *buf = new char[strlen(szIpRet) + 1];
					strcpy(buf, szIpRet);
					sendController(buf);
				}
			}
			else
			{
				break;
			}
		}
			
	}
	else
	{
		cout << "解析失败。" << endl;
	}
		
 
	cout << "hostnameToip结束" << endl;
	memset( szHostName, 0, sizeof(szHostName) );
	cout << "--------------------------------------" << endl;
	//cout << "输入域名:";
	
	WSACleanup();
	return 0;
}


int main()
{
	
	hostnameToIp();
	
		//cout << "请输入你要扫描的ip:" << endl;
		//cout << ">>";
		//char * ip = new char[50];
		//cin >> ip;
		//char szIpBuff[32] = { 0 };
		//cout << "域名转ip:" << szIpBuff << endl;

		//cout << "输入你要查询的起始端口:" << endl;
		//cout << ">>";
		//int startPort;
		//cin >> startPort;

		//cout << "输入你要查询的结束端口:" << endl;
		//cout << ">>";
		//int endPort;
		//cin >> endPort;

		//scant(ip, startPort, endPort);

	system("pause");
	return 0;
}


通过这次实验的学习,完成了端口扫描工具的优化。包括这四部分:域名转换IP地址、主机存活测试以及延时扫描等功能、多线程扫描。相对于上一次多使用多线程扫描,可以加快自己的端口扫描的速度。

多线程实现的方法:

设计一个线程函数

 

//线程函数

DWORD WINAPI ThreadProc(LPVOID lpParameter)

线程函数里面设定端口数量,端口号从0-65535,不断递增,使用临界区保证使用多线程时不会重复访问一个端口。

//进入临界区

EnterCriticalSection(&cs);

int tmpport = PortNum;

PortNum++;

//DWORD threadID=GetCurrentThreadId();

//printf("线程%d正在检测端口%d\n",threadID,PortNum);//所有使用临界区资源的代码都要加锁

//离开临界区

LeaveCriticalSection(&cs);

使用多线程扫描临界区,当线程数量超过64时,可以使用WaitForMultipleObjects函数处理,这个函数其实作用其实就是可以不让在一瞬间创建一大堆线程,而是以64个线程为一个单位,当其中某个线程结束时,再触发下一个单位的线程,放缓线程的创建速度。

 

//初始化临界区

InitializeCriticalSection(&cs);

//多线程扫描

HANDLE hThread[THREADCOUNT];

for (int i = 0; i < THREADCOUNT; i++)

{

hThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)0, 0, NULL);

}

 

//当thread数量超过64的处理

int tempNumThreads = THREADCOUNT;

int tempMax = 0;

while (tempNumThreads >= MAXIMUM_WAIT_OBJECTS)

{

tempNumThreads -= MAXIMUM_WAIT_OBJECTS;

WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, &hThread[tempMax], false, INFINITE);

tempMax += MAXIMUM_WAIT_OBJECTS;

}

WaitForMultipleObjects(tempNumThreads, &hThread[tempMax], false, INFINITE);

 

//删除临界区

DeleteCriticalSection(&cs);

 

 

(2)域名转换ip地址使用gethostbyname这个函数得到域名的相关信息,然后使用inet_ntop解析IP地址

const char* szIpRet = inet_ntop(pHostEntry->h_addrtype, *pptr, szIpBuff, sizeof(szIpBuff));

(3)存活测试适应ping程序看是否ping通,但是在此期间我遇到了一个问题,就是发送的数据包时间过长,没有接到回复,所以要设置超时时间。

// 设置接收超时

    setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));

4非阻塞与延时扫描使用

    TIMEVAL TimeOut;

FD_SET mask;

unsigned long mode = 1; //ipctlsocket函数的最后一个参数

 

//设置超时毫秒数

TimeOut.tv_sec = 0;

TimeOut.tv_usec = nTimeout;

FD_ZERO(&mask);

FD_SET(s, &mask);

 

//设置为非阻塞模式

ioctlsocket(s, FIONBIO, &mode);

 

connect(s, (struct sockaddr *)&server, sizeof(server)); //连接

int ret = select(0, NULL, &mask, NULL, &TimeOut);

 

猜你喜欢

转载自blog.csdn.net/guanshanyue96/article/details/92991609
今日推荐