请结合文档中的内容,编写一个利用多线程技术实现的端口扫描工具。(基于之前的博客网络安全12篇)
相关知识:
端口扫描背景及意义
网络中每台计算机犹如一座城堡,这些城堡中,有些是对外完全开放的,有些却是大门紧闭的。入侵者们是如何找到,并打开它们的城门呢?这些城门究竟通向何处? 在网络中,把这些城堡的“城门”称之为计算机的“端口”。端口扫描是入侵者搜索信息的几种常用方法之一,也正是这一种方法最容易暴露入侵者的身份和意图。一般说来,扫描端口有以下目的: 判断目标主机上开放了哪些服务 判断目标主机的操作系统 如果入侵者掌握了目标主机开放了哪些服务,运行何种操作系统,他们就能使用相应的手段实现入侵。而如果管理员先掌握了这些端口服务的安全漏洞,就能采取有效的安全措施,防范相应的入侵。
端口扫描现状
计算机信息网络的发展加速了信息化时代的进程,但是随着社会网络化程度的增加,对计算机网络的依赖也越来越大,网络安全问题也日益明显。端口扫描技术是发现安全问题的重要手段之一。一个端口就是一个潜在的通信通道,也就是一个入侵通道。对目标计算机进行端口扫描,能得到许多有用的信息。扫描器通过选用远程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);