16.1 Socket 端口扫描技术

news2024/11/18 6:46:21

端口扫描是一种网络安全测试技术,该技术可用于确定对端主机中开放的服务,从而在渗透中实现信息搜集,其主要原理是通过发送一系列的网络请求来探测特定主机上开放的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 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1113043.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java —— 程序逻辑控制

目录 1. 顺序结构 2. 分支结构 2.1 if 语句 2.1.1 语法格式1 2.1.2 语法格式2 2.1.3 语法格式3 2.2 switch 语句 3. 循环结构 3.1 while循环 3.2 break与continue 3.3 for循环 4. 输入输出 4.1 输出到控制台 格式化字符串 4.2 从键盘输入 5. 练习 和C语言类似地, Java的程序逻辑…

通讯协议学习之路:USB协议协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 1、…

服务CPU异常飙高问题分析和解决

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d; 一位上进心十足的【Java ToB端大厂…

R语言提取文字(字符串)中的内容--正则式(1)

科学研究中有时候咱们收集到的数据很乱&#xff0c;不能马上进行分析&#xff0c;如SEER数据&#xff0c;用过都知道&#xff0c;咱们需要对数据进行清洗&#xff0c;从数据中提取咱们需要的东西&#xff0c;才能进行分析&#xff0c;这时候有个有用的东西叫正则式&#xff0c;…

中文编程工具开发语言编程案例:会员VIP管理系统软件实例

中文编程工具开发语言编程案例&#xff1a;会员VIP管理系统软件实例 中文编程工具开发语言编程案例&#xff1a;会员VIP管理系统软件实例。 软件功能&#xff1a; 1、系统设置&#xff1a;参数设定&#xff0c;账号及权限设置&#xff0c;系统初始化&#xff0c;卡类型设置&a…

通讯协议学习之路:IrDA协议协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 序、…

Flutter的Invalid use of a private type in a public API警告

文章目录 问题描述有问题的源码 问题原因解决方法 问题描述 自己在写Flutter 应用时发现了一个Invalid use of a private type in a public API警告。 发现很多官方的例子也有这个问题。 有问题的源码 有问题的源码如下&#xff1a; class MyTabPage extends StatefulWid…

window系统如何管理多版本node

何时需要切换node版本 如果你正在维护一个旧项目&#xff0c;同时也在进行新项目&#xff0c;两个项目所依赖的node版本害不同&#xff0c;那么你可以就需要经常切换node版本。项目中可能依赖于某些npm包&#xff0c;而这些包对特定版本的Node有要求。需要满足这些要求以确保依…

UE4 UltrDynamicSky与场景物体进行交互

找到材质 找到其最父类的材质 把这个拖过去连上即可

Nvm管理NodeJs版本

文章目录 Nvm管理NodeJs版本一、前言1.简介2.环境 二、正文1.卸载NodeJs2.安装Nvm3.配置国内镜像4.Nvm使用5.其它1&#xff09;报错12&#xff09;报错2 Nvm管理NodeJs版本 一、前言 1.简介 Node Version Manager&#xff08;nvm&#xff09;可通过命令行快速安装和使用不同…

【数据库】拼接字段 使用别名

拼接字段 使用别名 e . g . e.g. e.g. Vendors 表包含供应商名和电话信息&#xff0c;name 和 mobile&#xff1b;需要输出这两个属性的值的组合作为供应商的基本信息组合。 SELECT concat(name, _, mobile) FROM Vendors; -- 语句通过 MySQL 环境下测试&#xff0c;其他 DBMS…

【CMN】Components组件汇总

CMN 700由各种类型的设备组成&#xff0c;包括路由器模块、CHI节点和网桥。所需要的组件取决于系统的需求&#xff0c;有些组件是可选的&#xff0c;或者只有在满足某些需求时才会使用。CMN 700可以集成到一个完整的SoC系统中&#xff0c;该系统还包括其他这里未描述到的设备。…

4.6 IPv6

思维导图&#xff1a; 4.6 IPv6 **IPv6简介** - IP 是互联网的核心协议。 - IPv4 是20世纪70年代末期设计的&#xff0c;到2011年2月其地址耗尽。 - 2014-2015年间&#xff0c;我国逐步停止向新用户分配IPv4地址&#xff0c;开始全面部署IPv6。 - IPv6 目的&#xff1a;解决I…

JDK安装后Path和java_home环境变量

1.Path环境变量 1.1Path环境变量用于记住程序路径&#xff0c;方便在命令行窗口的任意目录启动程序 举例&#xff1a;在命令行窗口的任意目录下启动QQ Path环境部变量的位置在&#xff1a;我的电脑->属性->高级系统设置->高级->环境变量 2.2Path环境变量的原理 …

(1)(1.7) GY-US42声纳

文章目录 前言 1 连接方式 2 参数说明 前言 GY-US042v2 声纳是一款非常便宜的短程&#xff08;最多 4m&#xff09;测距仪&#xff0c;主要设计用于室内&#xff0c;但也成功用于室外的 Copter。与许多气压计相比&#xff0c;它能更稳定地控制 4m 以下的高度。 1 连接方式…

《动手学深度学习 Pytorch版》 9.5 机器翻译与数据集

机器翻译&#xff08;machine translation&#xff09;指的是将序列从一种语言自动翻译成另一种语言&#xff0c;基于神经网络的方法通常被称为神经机器翻译&#xff08;neural machine translation&#xff09;。 import os import torch from d2l import torch as d2l9.5.1 …

【Linux】nginx基础篇 -- 介绍及yum安装nginx

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

1024程序节特辑:一文读懂小程序支付流程

小程序支付流程 概述前置准备登录流程调用wx.login()向微信服务器发送请求 支付流程调用wx.requestPayment()部分后台处理逻辑支付功能要求 支付流程面试题 主页传送门&#xff1a;&#x1f4c0; 传送 概述 小程序支付是由微信支付推出的一种便捷支付方式&#xff0c;通过扫码…

快速实现 RPC 微服务:使用 go-micro 从零开始

大家好&#xff0c;我是木川 本文将演示如何从 0 到 1 使用 go-micro 框架实现 RPC 微服务&#xff0c;包括服务端和客户端&#xff0c;客户端发送一个名字到服务端&#xff0c;服务端返回问候语。 一、创建项目 创建项目目录&#xff0c;使用 go mod 初始化 mkdir goexamples …

前端课后作业

2023.10.20 1、用列表实现如下效果 2、用表格实现如下效果 3、用表单实现如下效果 &#xff08;学到16集完成&#xff09;