端口扫描是一种网络安全测试技术,该技术可用于确定对端主机中开放的服务,从而在渗透中实现信息搜集,其主要原理是通过发送一系列的网络请求来探测特定主机上开放的TCP/IP端口。具体来说,端口扫描程序将从指定的起始端口开始,向目标主机发送一条TCPUDP消息(这取决于端口的协议类型)。如果目标主机正在监听该端口,则它将返回一个确认消息,这表明该端口是开放的。如果没有响应,则说明该端口是关闭的或被过滤。

首先我们来了解一下阻塞与非阻塞模式:

  • 阻塞模式是指当I/O操作无法立即完成时,应用程序会阻塞并等待操作完成。例如,在使用阻塞套接字接收数据时,如果没有数据可用,则调用函数将一直阻塞,直到有数据可用为止。在这种模式下,I/O操作将会一直阻塞应用程序的进程,因此无法执行其他任务。

  • 非阻塞模式是指当I/O操作无法立即完成时,应用程序会立即返回并继续执行其他任务。例如,在使用非阻塞套接字接收数据时,如果没有数据可用,则调用函数将立即返回,并指示操作正在进行中,同时应用程序可以执行其他任务。在这种模式下,应用程序必须反复调用I/O操作以检查其完译状态,这通常是通过轮询或事件通知机制实现的。非阻塞模式允许应用程序同时执行多个任务,但每个I/O操作都需要增加一定的额外开销。

要实现端口探测我们可以通过connect()这个函数来实现,利用connect函数实现端口开放检查的原理是通过TCP协议的三次握手过程来探测目标主机是否开放目标端口。

TCP协议的三次握手过程中,客户端向服务器发送一个SYN标志位的TCP数据包。如果目标主机开放了目标端口并且正在监听连接请求,则服务器会返回一个带有SYNACK标志位的TCP数据包,表示确认连接请求并请求客户端确认。此时客户端回应一个ACK标志位的TCP数据包,表示确认连接请求,并建立了一个到服务器端口的连接。此时客户端和服务器端之间建立了一个TCP连接,可以进行数据传输。

如果目标主机没有开放目标端口或者目标端口已经被占用,则服务器不会响应客户端的TCP数据包,客户端会在一定时间后收到一个超时错误,表示连接失败。

因此,通过调用connect函数,可以向目标主机发送一个SYN标志位的TCP数据包并等待服务器响应,从而判断目标端口是否开放。如果connect函数返回0,则表示连接成功,目标端口开放;否则,连接失败,目标端口未开放或目标主机不可达。

// 探测网络端口开放情况
BOOL PortScan(char *Addr, int Port)
{
WSADATA wsd;
SOCKET sHost;
SOCKADDR_IN servAddr; // 初始化套接字库
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
return FALSE;
} // 创建套接字
sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sHost)
{
return FALSE;
} // 设置连接地址和端口
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr(Addr);
servAddr.sin_port = htons(Port); // 连接测试
int retval = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));
if (retval != SOCKET_ERROR)
{
return TRUE;
} WSACleanup();
closesocket(sHost);
return FALSE;
} int main(int argc, char* argv[])
{
int port_list[] = { 80, 443, 445, 135, 139, 445 };
int port_size = sizeof(port_list) / sizeof(int); for (int x = 0; x < port_size; x++)
{
int ret = PortScan("8.141.58.64", port_list[x]);
printf("循环次数: %d 端口: %d 状态: %d \n", x + 1, port_list[x], ret);
} system("pause");
return 0;
}

上述代码片段则是一个简单的端口探测案例,当运行后程序会调用connect函数向目标主机发送一个SYN标志位的TCP数据包,探测目标端口是否开放。如果目标主机响应带有SYNACK标志位的TCP数据包,则表示连接请求成功并请求确认,操作系统在自动发送带ACK标志位的TCP数据包进行确认,建立TCP连接;

如果目标主机没有响应或者响应带有RST标志位的TCP数据包,则表示连接请求失败,目标端口为未开放状态。通过此方式,程序可以快速检测多个端口是否开放,该程序运行后输出效果如下图所示;

上述代码虽然可以实现端口扫描,但是读者应该会发现此方法扫描很慢,这是因为扫描器每次只能链接一个主机上的端口只有当connect函数返回后才会执行下一次探测任务,而如果需要提高扫描效率那么最好的方法是采用非阻塞的扫描模式,使用非阻塞模式我们可以在不使用多线程的情况下提高扫描速度。

非阻塞模式所依赖的核心函数为select()函数是一种用于多路I/O复用的系统调用,在Windows中提供了对该系统调用的支持。select()函数可以同时监听多个文件或套接字(socket)的可读、可写和出错状态,并返回有状态变化的文件或套接字的数量,在使用该函数时读者应率先调用ioctlsocket()函数,并设置FIONBIO套接字为非阻塞模式。

select 函数的基本语法如下:

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

参数解释:

  • nfds:需要监听的文件或套接字最大编号加1
  • readfds:可读文件或套接字集合
  • writefds:可写文件或套接字集合
  • exceptfds:出错文件或套接字集合
  • timeout:超时时间,如果为NULL,则表示一直等待直到有事件发生

select 函数会阻塞进程,直到在需要监听的文件或套接字中有一个或多个文件或套接字发送了需要监听的事件,或者超时时间到达。当select()函数返回时,可以通过fd_set集合来查询有状态变化的文件或套接字。

select 函数的原理是将调用进程的文件或套接字加入内核监测队列,等待事件发生。当某个文件或套接字有事件发生时,内核会将其添加到内核缓冲区中,同时在返回时告诉进程有哪些套接字可以进行I/O操作,进程再根据文件或套接字的状态进行相应的处理。使用select()函数可以大大提高I/O操作的效率,减少资源占用。

如下代码实现的是一段简单的端口扫描程序,用于检查目标主机的一段端口范围内是否有端口处于开放状态。该函数中通过设置fd_set类型的掩码(mask)并加入套接字,使用select()函数查询该套接字的可写状态,并设置超时时间为1毫秒,如果返回值为0,则目标端口未开放,继续下一个端口的扫描。如果返回值为正数,则目标端口已成功连接(开放),输出扫描结果并继续下一个端口的扫描。

该代码中使用了非阻塞套接字和select()函数的组合来实现非阻塞IO。非阻塞套接字可以使程序不会在等待数据到来时一直阻塞,而是可以在等待数据到来的同时进行其他操作,从而提高程序的效率。select()函数则可以同时等待多个套接字的数据到来,从而使程序更加高效地进行I/O操作。

// 非阻塞端口探测
void PortScan(char *address, int StartPort, int EndPort)
{
SOCKADDR_IN ServAddr;
TIMEVAL TimeOut;
FD_SET mask; TimeOut.tv_sec = 0; // 设置超时时间为500毫秒
TimeOut.tv_usec = 1000;
// 指定模式
unsigned long mode = 1; // 循环扫描端口
for (int port = StartPort; port <= EndPort; port++)
{
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ServAddr.sin_family = AF_INET;
ServAddr.sin_addr.S_un.S_addr = inet_addr(address);
ServAddr.sin_port = htons(port); FD_ZERO(&mask);
FD_SET(sock, &mask); // 设置为非阻塞模式
ioctlsocket(sock, FIONBIO, &mode);
connect(sock, (struct sockaddr *)&ServAddr, sizeof(ServAddr)); // 查询可写入状态 如果不为0则说明这个端口是开放的
int ret = select(0, 0, &mask, 0, &TimeOut);
if (ret != 0 && ret != -1)
{
printf("扫描地址: %-13s --> 端口: %-5d --> 状态: [Open] \n", address, port);
}
else
{
printf("扫描地址: %-13s --> 端口: %-5d --> 状态: [Close] \n", address, port);
}
}
} int main(int argc, char *argv[])
{
char *Addr[2] = { "192.168.1.1", "192.168.1.10" }; WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
exit(0);
} for (int x = 0; x < 2; x++)
{
PortScan(Addr[x], 1, 255);
} WSACleanup(); system("pause");
return 0;
}

读者可自行编译并运行上述代码片段,默认会扫描Addr[2]数组内的两个IP地址的1-255端口范围开放情况,读者可感觉到效率上变得快了许多,输出效果如下图所示;

上述代码虽然增加的扫描速度但是还可以进一步优化,我们可以通过增加信号机制,通过使用信号可以很好的控制扫描并发连接数,增加了线程控制将会使扫描器更加稳定,同时我们还引用了多线程模式,通过两者的结合可以极大的提高扫描质量和效率。

基于信号的端口扫描,也称为异步IO端口扫描,是一种高效的端口扫描技术,可以利用操作系统的信号机制提高网络I/O的效率。基于信号的端口扫描具有非阻塞和异步的特性,可以最大限度地提高网络I/O效率,同时在大并发量下表现出更好的性能。但是,使用时需要小心处理信号的相关问题,避免死锁和数据不一致。

#include <stdio.h>
#include <winsock2.h> #pragma comment (lib, "ws2_32") typedef struct _THREAD_PARAM
{
char *HostAddr; // 扫描主机
DWORD dwStartPort; // 端口号
HANDLE hEvent; // 事件句柄
HANDLE hSemaphore; // 信号量句柄
}THREAD_PARAM; // 最大线程数,用于控制信号量数量
#define MAX_THREAD 10 // 线程扫描函数
DWORD WINAPI ScanThread(LPVOID lpParam)
{
// 拷贝传递来的扫描参数
THREAD_PARAM ScanParam = { 0 };
MoveMemory(&ScanParam, lpParam, sizeof(THREAD_PARAM)); // 设置信号
SetEvent(ScanParam.hEvent); WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa); // 初始化套接字
SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockaddr; // 填充扫描地址与端口
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.S_un.S_addr = inet_addr(ScanParam.HostAddr);
sockaddr.sin_port = htons(ScanParam.dwStartPort); // 开始连接
if (connect(s, (SOCKADDR*)&sockaddr, sizeof(SOCKADDR)) == 0)
{
printf("地址: %-16s --> 端口: %-5d --> 信号量: %-5d 状态: [Open] \n",
ScanParam.HostAddr, ScanParam.dwStartPort, ScanParam.hSemaphore);
}
else
{
printf("地址: %-16s --> 端口: %-5d --> 信号量: %-5d 状态: [Close] \n",
ScanParam.HostAddr, ScanParam.dwStartPort, ScanParam.hSemaphore);
} closesocket(s);
WSACleanup(); // 释放一个信号量
ReleaseSemaphore(ScanParam.hSemaphore, 1, NULL);
return 0;
} int main(int argc, char *argv[])
{
// 线程参数传递
THREAD_PARAM ThreadParam = { 0 }; // 设置线程信号
SetEvent(ThreadParam.hEvent); // 创建事件
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建信号
HANDLE hSemaphore = CreateSemaphore(NULL, MAX_THREAD, MAX_THREAD, NULL); ThreadParam.hEvent = hEvent;
ThreadParam.hSemaphore = hSemaphore;
ThreadParam.HostAddr = "59.110.117.109"; for (DWORD port = 1; port < 4096; port++)
{
// 判断信号量
DWORD dwWaitRet = WaitForSingleObject(hSemaphore, 200);
if (dwWaitRet == WAIT_OBJECT_0)
{
ThreadParam.dwStartPort = port; // 启动扫描线程
HANDLE hThread = CreateThread(NULL, 0, ScanThread, (LPVOID)&ThreadParam, 0, NULL); // 等待事件
WaitForSingleObject(hEvent, INFINITE); // 重置信号
ResetEvent(hEvent);
}
else if (dwWaitRet == WAIT_TIMEOUT)
{
continue;
}
} system("pause");
return 0;
}

读者可自行编译并运行上述代码,将对特定IP地址进行端口探测,每次启用10个线程,即实现了控制线程并发,又实现了端口多线程扫描效果,如下图所示;

本文作者: 王瑞

本文链接: https://www.lyshark.com/post/e9090338.html

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

16.1 Socket 端口扫描技术的更多相关文章

  1. Hacker(16)----防范端口扫描与嗅探

    端口扫描与嗅探都是黑客常用的招数,其目的是定位目标计算机和窃取隐私信息.为确保自己计算机的安全,用户需要掌握防范嗅探与端口扫描的常见措施,保障个人隐私信息安全. 一.掌握防范端口扫描的常用措施 防范端 ...

  2. 一、TCP扫描技术

    一.TCP扫描技术 常用的端口扫描技术有很多种,如 TCP connect() 扫描 .TCP SYN 扫描.TCP FIN 扫描 等,网络上也有很多文章专门介绍,比如 :http://www.ant ...

  3. 小白日记10:kali渗透测试之端口扫描-UDP、TCP、僵尸扫描、隐蔽扫描

    端口扫描 二三四层发现的目的只是为了准确发现所有活着主机IP,确定攻击面,端口扫描即发现攻击点,发现开放端口.端口对应网络服务及应用端程序,服务端程序的漏洞通过端口攻入.[所有的扫描结果,都不要完全相 ...

  4. Kali linux 2016.2(Rolling)中metasploit的端口扫描

    目前常见的端口扫描技术一般有如下几类: TCP  Connect.TCP SYN.TCP ACK.TCP FIN. Metasploit中的端口扫描器 Metasploit的辅助模块中提供了几款实用的 ...

  5. 端口复用技术简单了解;重用端口;socket复用端口

    端口复用相关点 多个应用复用端口,只有最后一个绑定的socket可以接受数据,所有socket都可以发送数据 使用端口复用技术时,所有的socket都开启端口复用,才可以实现端口复用 黑客技术,使用标 ...

  6. 端口扫描程序nmap使用手册

        其实还是建议看英文的man,对以后学习其他东西很有帮助的:) 摘要 nmap是一个网络探测和安全扫描程序,系统管理者和个人可以使用这个软件扫描大型的网络,获取那台主机正在运行以及提供什么服务等 ...

  7. ★Kali信息收集★8.Nmap :端口扫描

    ★Kali信息收集~ 0.Httrack 网站复制机 http://www.cnblogs.com/dunitian/p/5061954.html ★Kali信息收集~ 1.Google Hackin ...

  8. Kail Linux渗透测试教程之ARP侦查Netdiscover端口扫描Zenmap与黑暗搜索引擎Shodan

    Kail Linux渗透测试教程之ARP侦查Netdiscover端口扫描Zenmap与黑暗搜索引擎Shodan ARP侦查工具——Netdiscover Netdiscover是一个主动/被动的AR ...

  9. 用Python实现一个端口扫描,只需简单几步就好

    一.常见端口扫描的原理 0.秘密扫描 秘密扫描是一种不被审计工具所检测的扫描技术. 它通常用于在通过普通的防火墙或路由器的筛选(filtering)时隐藏自己. 秘密扫描能躲避IDS.防火墙.包过滤器 ...

  10. Kali linux 2016.2(Rolling)中的Nmap的端口扫描功能

    不多说,直接上干货! 如下,是使用Nmap对主机202.193.58.13进行一次端口扫描的结果,其中使用 root@kali:~# nmap -sS -Pn 202.193.58.13 Starti ...

随机推荐

  1. selenium实战学习--定位元素

    from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.common import ...

  2. Vue基础介绍

    一.Vue基本介绍 1.Vue.js目前最火的的一个前端框架,三大主流前端框架之一.与其他重量级框架不同的是,Vue采用自底向上增量开发的设计.Vue的核心库只关注视图层. 2.Vue.js是一套构建 ...

  3. 【Qt 应用】模仿实现Win10的Wifi列表

    这里使用 Qt 模仿实现了 Win10 系统下的 Wifi 列表,主要用的是 QlistWidget + xml + cmd命令行 实现. 效果 下载地址 https://github.com/con ...

  4. load initialize总结

    load initialize 方法的区别1.调用的方式 - load 根据函数地址调用 - initialize 通过objc_msgsend调用 2.调用时刻 - load runtime 加载类 ...

  5. CocosCreator基础

    跳转到底部 目录 CocosCreator项目结构: 资源文件夹(assets) 资源库(library) 本地设置(local) 项目设置(settings) project.json 构建目标(b ...

  6. Vue-treeselect 实现下拉树懒加载,末节点不要箭头

    项目需要,可选择的下拉树,由于数据过多,显示要有层级感,所以使用了懒加载模式 vue-treeselect官网:https://www.vue-treeselect.cn/ 1.前端代码 1.下载依赖 ...

  7. Python异常模块与包

    Python异常模块与包 一.了解异常 1.1 什么是异常 当检测到一个错误时,Python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常", 也就是我们常说 ...

  8. Nextcloud允许不被信任的域访问 取消 trusted domains

    在服务器部署了Nextcloud,由于测试需要,经常从不同的地址访问,但是每次访问都要把域名添加到受信任域,反反复复修改也挺麻烦,暂时又没找到通配符或者禁用的方法. 不过网上提供了一个替代方法,动态生 ...

  9. 硬件管理平台 - 公共项目搭建(Nancy部分)

    项目变更 之前使用的是Nancy库进行项目搭建的,使用的Nuget版本及其他引用如下 <?xml version="1.0" encoding="utf-8&quo ...

  10. linux下卸载vnc

    sudo apt-get purge realvnc-vnc-server推荐连接:https://askubuntu.com/questions/653321/how-to-uninstall-re ...