运用Npcap库实现SYN半开放扫描

news/2024/11/16 6:22:17/文章来源:https://www.cnblogs.com/LyShark/p/18351932

Npcap 是一款高性能的网络捕获和数据包分析库,作为 Nmap 项目的一部分,Npcap 可用于捕获、发送和分析网络数据包。本章将介绍如何使用 Npcap 库来实现半开放扫描功能。TCP SYN 半开放扫描是一种常见且广泛使用的端口扫描技术,用于探测目标主机端口的开放状态。由于这种方法并不完成完整的 TCP 三次握手过程,因此具有更高的隐蔽性和扫描效率。

笔者原本想为大家整理并分享如何使用Nmap工具进行端口扫描的,但觉得仅仅讲解Nmap的命令使用方法并不能让大家更好地理解其工作原理。实际上,Nmap 的底层使用的是Npcap库,因此笔者决定演示如何使用Npcap库开发一个简单的扫描功能,从而帮助大家更好地理解Nmap的原理。

首先,若使用Nmap对目标主机进行SYN扫描,只需要执行nmap -sS 39.97.203.57命令即可,等待一段时间则可获取到目标主机常规开放端口状态,若要扫描特定端口开放状态仅需要指定-p参数并携带扫描区间即可,如下命令所示;

┌──(lyshark㉿kali)-[~]
└─$ sudo nmap -sS 39.97.203.57
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-08 15:28 CST
Nmap scan report for 39.97.203.57
Host is up (0.0038s latency).
Not shown: 997 filtered tcp ports (no-response)
PORT     STATE SERVICE
80/tcp   open  http
443/tcp  open  https
1935/tcp open  rtmp┌──(lyshark㉿kali)-[~]
└─$ sudo nmap -sS -v 39.97.203.57 -p 1-2000
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-08 15:32 CST
Scanning 39.97.203.57 [2000 ports]
Discovered open port 80/tcp on 39.97.203.57
Discovered open port 443/tcp on 39.97.203.57
Discovered open port 1935/tcp on 39.97.203.57
Completed SYN Stealth Scan at 15:32, 7.42s elapsed (2000 total ports)
Nmap scan report for 39.97.203.57
Host is up (0.0039s latency).
Not shown: 1997 filtered tcp ports (no-response)
PORT     STATE SERVICE
80/tcp   open  http
443/tcp  open  https
1935/tcp open  rtmp

Npcap库的配置非常简单,读者仅需要去到官网下载,初次使用还需安装Npcap 1.79 installer驱动程序,并下载Npcap SDK 1.13对应的开发工具包,如下图所示;

接着,读者需要自行解压SDK开发工具包,并配置VC++目录包含目录与库目录,如下图所示;

在进行开发之前,我们需要先定义三个结构体变量,首先定义eth_header数据包头,以太网包头(Ethernet Frame Header)用于传输控制信息和数据,它是数据链路层的一部分,负责在局域网中实现数据的可靠传输。

接着定义ip_header数据包头,IP头(IP Header)用于传输控制信息和数据,IP头是网络层的一部分,负责实现跨越不同网络的数据传输。

最后定义tcp_header数据包头,TCP头(TCP Header)用于传输控制信息和数据,TCP头是传输层的一部分,负责在主机之间提供可靠的、面向连接的通信。

若要发送TCP数据包,必须要构造一个完整的通信协议头,将以太网数据包头、IP数据包头、TCP数据包头封装起来即可,其定义部分如下所示,其中每一个变量均对应于协议的每一个参数。

#include <winsock2.h>
#include <Windows.h>
#include <pcap.h>#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "wpcap.lib")// 以太网头部结构体
struct eth_header
{uint8_t dest[6];   // 目的MAC地址 (6字节)uint8_t src[6];    // 源MAC地址 (6字节)uint16_t type;     // 以太网类型字段,表示上层协议 (2字节)
};// IPv4头部结构体
struct ip_header
{uint8_t ihl : 4,     // 头部长度 (4位),表示IP头部的长度,以32位字为单位version : 4; // 版本 (4位),IPv4的版本号为4uint8_t tos;        // 服务类型 (1字节)uint16_t tot_len;   // 总长度 (2字节),表示整个IP数据报的长度,以字节为单位uint16_t id;        // 标识 (2字节),用于标识数据报片段uint16_t frag_off;  // 片段偏移 (2字节),用于数据报片段uint8_t ttl;        // 生存时间 (1字节),表示数据报在网络中的生存时间uint8_t protocol;   // 协议 (1字节),表示上层协议 (例如,TCP为6,UDP为17)uint16_t check;     // 头部校验和 (2字节),用于检验头部的完整性uint32_t saddr;     // 源地址 (4字节),表示发送方的IPv4地址uint32_t daddr;     // 目的地址 (4字节),表示接收方的IPv4地址
};// TCP头部结构体
struct tcp_header
{uint16_t source;    // 源端口号 (2字节)uint16_t dest;      // 目的端口号 (2字节)uint32_t seq;       // 序号 (4字节),表示数据段的序列号uint32_t ack_seq;   // 确认号 (4字节),表示期望接收的下一个序列号uint16_t res1 : 4,  // 保留位 (4位),通常设为0doff : 4,   // 数据偏移 (4位),表示TCP头部的长度,以32位字为单位fin : 1,    // FIN标志 (1位),表示发送方没有更多数据syn : 1,    // SYN标志 (1位),表示同步序号,用于建立连接rst : 1,    // RST标志 (1位),表示重置连接psh : 1,    // PSH标志 (1位),表示推送数据ack : 1,    // ACK标志 (1位),表示确认字段有效urg : 1,    // URG标志 (1位),表示紧急指针字段有效res2 : 2;   // 保留位 (2位),通常设为0uint16_t window;    // 窗口大小 (2字节),表示接收方的缓冲区大小uint16_t check;     // 校验和 (2字节),用于检验TCP头部和数据的完整性uint16_t urg_ptr;   // 紧急指针 (2字节),表示紧急数据的偏移量
};unsigned short checksum(void *b, int len)
{unsigned short *buf = (unsigned short *)b;unsigned int sum = 0;unsigned short result;for (sum = 0; len > 1; len -= 2)sum += *buf++;if (len == 1)sum += *(unsigned char*)buf;sum = (sum >> 16) + (sum & 0xFFFF);sum += (sum >> 16);result = ~sum;return result;
}

接着需要实现两个通用函数,其中EnumAdapters用于枚举当前系统中所有的网卡信息,并输出其下标号与网卡描述信息,BindAdapters函数则用于根据用户传入的下标号对网卡进行动态绑定,函数中通过循环的方式查找网卡下标若匹配则将下标所对应的句柄存储到temp_adapter变量内,最后通过pcap_open_live实现对网卡的打开。

// 枚举当前网卡
int EnumAdapters()
{pcap_if_t *allAdapters;pcap_if_t *ptr;int index = 0;char errbuf[PCAP_ERRBUF_SIZE];// 获取本地机器设备列表if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &allAdapters, errbuf) != -1){// 打印网卡信息列表for (ptr = allAdapters; ptr != NULL; ptr = ptr->next){++index;if (ptr->description){printf("[ %d ] \t [ %s ] \n", index - 1, ptr->description);}}}pcap_freealldevs(allAdapters);return index;
}// 根据编号绑定到对应网卡
pcap_t* BindAdapters(int nChoose)
{pcap_if_t *adapters, *temp_adapter;char errbuf[PCAP_ERRBUF_SIZE];pcap_t *handle = NULL;if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &adapters, errbuf) == -1){return NULL;}// 遍历找到指定的网卡temp_adapter = adapters;for (int x = 0; x < nChoose - 1 && temp_adapter != NULL; ++x){temp_adapter = temp_adapter->next;}// 若找不到绑定设备则释放句柄if (temp_adapter == NULL){pcap_freealldevs(adapters);return NULL;}// 打开指定的网卡handle = pcap_open_live(temp_adapter->name, 65534, PCAP_OPENFLAG_PROMISCUOUS, 1000, errbuf);if (handle == NULL){pcap_freealldevs(adapters);return NULL;}pcap_freealldevs(adapters);return handle;
}

抓包回调函数packet_handlerpcap_loop调用,当启用抓包后若句柄返回数据则会通过回调函数通知用户,用户获取到数据包header后,通过逐层解析即可得到所需要的字段,若要实现SYN快速探测则需要判断tcph标志,若标志被返回则可通过RST断开会话,并以此节约扫描时间。

如下代码,定义了一个网络数据包回调函数 packet_handler,用于处理通过 pcap 库捕获的网络数据包。函数首先打印数据包的长度,然后解析以太网头部以检查其类型是否为 IP(0x0800)。如果是 IP 数据包,进一步解析 IP 头部并打印相关信息,包括 IP 版本、头长度、源 IP 地址和目标 IP 地址。随后检查 IP 数据包的协议字段是否为 TCP(6),若是,则解析 TCP 头部并打印源端口、目标端口、序列号、确认号、头部长度、标志、窗口大小、校验和及紧急指针等信息。

// 网络数据包回调函数
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{// 打印数据包长度printf("数据包长度:%d\n", header->len);// 以太网头部struct eth_header *eth = (struct eth_header *)(pkt_data);// 检查以太网类型是否为 IP(0x0800)if (ntohs(eth->type) == 0x0800){// IP 头部struct ip_header *iph = (struct ip_header *)(pkt_data + sizeof(struct eth_header));// 打印 IP 头部信息printf("IP 版本: %d | ", iph->version);printf("IP 头长度: %d | ", iph->ihl * 4);printf("源IP地址: %s | ", inet_ntoa(*(struct in_addr *)&iph->saddr));printf("目标IP地址: %s\n", inet_ntoa(*(struct in_addr *)&iph->daddr));// 检查协议是否为 TCP(6)if (iph->protocol == 6){// TCP 头部struct tcp_header *tcph = (struct tcp_header *)(pkt_data + sizeof(struct eth_header) + iph->ihl * 4);// 打印 TCP 头部信息printf("源端口: %d | ", ntohs(tcph->source));printf("目标端口: %d | ", ntohs(tcph->dest));printf("序列号: %u | ", ntohl(tcph->seq));printf("确认号: %u | ", ntohl(tcph->ack_seq));printf("包头长度: %d | ", tcph->doff * 4);printf("标志: ");if (tcph->fin) printf("FIN ");if (tcph->syn) printf("SYN ");if (tcph->rst) printf("RST ");if (tcph->psh) printf("PSH ");if (tcph->ack) printf("ACK ");if (tcph->urg) printf("URG ");printf("\n");printf("窗体长度: %d | ", ntohs(tcph->window));printf("校验和: 0x%04x | ", ntohs(tcph->check));printf("紧急数据指针: %d\n", ntohs(tcph->urg_ptr));}}printf("\n");
}

最后来看下主函数是如何实现的,首先通过调用EnumAdapters函数获取到网卡编号,并调用BindAdapters(4)函数绑定到指定的网卡之上,套接字的创建依然采用原生API接口来实现,只不过在调用sendto发送数据包时我们需要自行构建一个符合SYN扫描条件的数据包,在构建数据包时,以太网数据包用于指定网卡MAC地址等信息,IP数据包头则用于指定IP地址等信息,TCP数据包头则用于指定端口号信息,并仅需将tcph->syn = 1;设置为1,通过checksum计算校验和,并将校验好的packet包通过sendto函数发送到对端主机,如下所示;

int main(int argc, char* argv[])
{pcap_if_t *alldevs;pcap_t *adhandle;int i = 0;EnumAdapters();adhandle = BindAdapters(4);// 创建套接字SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);if (sock == INVALID_SOCKET){return -1;}// 设置套接字属性int one = 1;if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) == SOCKET_ERROR){return -1;}// ---------------------------------------------------------------// 构建网络数据包// ---------------------------------------------------------------char packet[4096];memset(packet, 0, 4096);struct eth_header *eth = (struct eth_header *)packet;struct ip_header *iph = (struct ip_header *)(packet + sizeof(struct eth_header));struct tcp_header *tcph = (struct tcp_header *)(packet + sizeof(struct eth_header) + sizeof(struct ip_header));// ---------------------------------------------------------------// 构建以太网数据包头// ---------------------------------------------------------------memset(eth->dest, 0xff, 6);  // 目标MAC地址memset(eth->src, 0x00, 6);   // 原MAC地址eth->type = htons(0x0800);   // IPv4// ---------------------------------------------------------------// 构建IP数据包头// ---------------------------------------------------------------iph->ihl = 5;iph->version = 4;iph->tos = 0;iph->tot_len = sizeof(struct ip_header) + sizeof(struct tcp_header);iph->id = htons(54321);iph->frag_off = 0;iph->ttl = 255;iph->protocol = IPPROTO_TCP;iph->check = 0;iph->saddr = inet_addr("192.168.1.1");   // 原始IP地址iph->daddr = inet_addr("39.97.203.57");  // 目标IP地址// ---------------------------------------------------------------// 构建TCP数据包头// ---------------------------------------------------------------tcph->source = htons(12345);           // 原始TCP端口tcph->dest = htons(80);                // 目标TCP端口tcph->seq = 0;tcph->ack_seq = 0;tcph->doff = 5; // TCP 头部长度tcph->fin = 0;tcph->syn = 1;tcph->rst = 0;tcph->psh = 0;tcph->ack = 0;tcph->urg = 0;tcph->window = htons(5840);    // 分配Windows窗体数tcph->check = 0;               // 现在保留校验和0,稍后用伪标头填充tcph->urg_ptr = 0;// ---------------------------------------------------------------// 计算校验和// ---------------------------------------------------------------// 计算IP校验和iph->check = checksum((unsigned short *)packet, iph->tot_len);// TCP 校验和struct{uint32_t src_addr;uint32_t dst_addr;uint8_t placeholder;uint8_t protocol;uint16_t tcp_length;struct tcp_header tcp;} pseudo_header;pseudo_header.src_addr = iph->saddr;pseudo_header.dst_addr = iph->daddr;pseudo_header.placeholder = 0;pseudo_header.protocol = IPPROTO_TCP;pseudo_header.tcp_length = htons(sizeof(struct tcp_header));memcpy(&pseudo_header.tcp, tcph, sizeof(struct tcp_header));tcph->check = checksum((unsigned short *)&pseudo_header, sizeof(pseudo_header));// ---------------------------------------------------------------// 发送数据包// ---------------------------------------------------------------struct sockaddr_in dest;dest.sin_family = AF_INET;dest.sin_addr.s_addr = iph->daddr;if (sendto(sock, packet, iph->tot_len, 0, (struct sockaddr *)&dest, sizeof(dest)) == SOCKET_ERROR){return -1;}// ---------------------------------------------------------------// 启用抓包// ---------------------------------------------------------------pcap_loop(adhandle, 10, packet_handler, NULL);pcap_close(adhandle);closesocket(sock);pcap_freealldevs(alldevs);system("pause");return 0;
}

读者可自行编译并运行上述代码,当执行成功后则可看到数据包的方向及标志类型,如下图所示。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js模拟Sleep/Delay

原文链接 https://www.51cto.com/article/767574.html 我的总结 看上去比较好的方式: 1.用递增的setTimeOut。 2.用async/await。 最后结论截图 翻译 搜索 复制

支付三大黑盒之二支付引擎

支付系统有三大黑盒“清结算对账、支付引擎和账务系统”,之所以说是黑盒一来是因为他们深藏后台很少被人看到,二来是有会计知识的门槛。这篇文章就用尽可能大白话的语言来介绍三个黑盒之一的“支付引擎”。 一、什么是支付引擎 支付引擎又被称为支付核心,他是支付系统的后台…

你觉得大模型时代该出现什么?

大模型的概念都火了两年了,之前各种媒体吹嘘大模型的出现是类似“蒸汽机时代”、“iPhone时刻”等等。那为什么我们期待的结果都没出现呢?咱们先一起回顾下历史。大模型的概念都火了两年了,之前各种媒体吹嘘大模型的出现是类似“蒸汽机时代”、“iPhone时刻”等等。那为什么…

鸿蒙(HarmonyOS)实现隐私政策弹窗

在实现用户协议弹窗时,通常我们会想到使用系统自定义弹窗,并在弹窗中点击跳转到Web页面。但在HarmonyOS中,由于系统弹窗的显示优先级高于其他组件,即使跳转到Web页面,弹窗依然会显示在最上层。 为了解决这个问题,我们可以自定义一个组件来模拟弹窗,这样当跳转到Web页面时…

软件测试常见术语-几万万人的钉钉、微信软件测试群

3 术语和定义 在本文档中,以下术语和定义适用。 ISO、IEC 和 IEEE 在以下地址维护标准化中使用的术语数据库:ISO 在线浏览平台:https://www.iso.org/obp/ui IEC 电子百科全书:网址:https://www.electropedia.org/ IEEE 在线标准词典:https://ieeexplore.ieee.org/browse/…

dotnet C# 分享基础 for 循环的写法

本文将来和大家聊聊在 dotnet C# 里面的基础的 for 循环语法的写法使用 for 作为循环的写法是在编程里面常用的代码写法。比如说我有一个名为 Foo 的类型,这个类型提供了一个名为 GetCount 的方法,这个方法可以缓慢的获取 Foo 里面的一个代表数量的值。且在 Foo 类型里面提供…

dotnet C# 使用 SHFileOperation 调用 Win32 的文件复制对话框

本文将和大家介绍如何在 dotnet C# 里面使用 SHFileOperation 调用 Windows 自带的文件复制对话框进行文件复制本文核心代码拷贝自 C#中使用SHFileOperation调用Windows的复制文件对话框 - 季风哥 - 博客园 文章,特别感谢大佬提供的方法 实现的效果图如下详细实现逻辑还请大家…

Avalonia 11.1 已知问题 应用启动时 PointToScreen 无法获取正确坐标

本文记录 Avalonia 11.1 版本的已知问题,在 Linux 上使用 X11 时,在应用启动时,即使在 Loaded 或 Activated 事件里,都无法使用 PointToScreen 获取到正确的屏幕坐标,只会将传入的点作为返回值此问题已经报告给 Avalonia 官方,请看 https://github.com/AvaloniaUI/Avalon…

乌龟对对碰开局应该抽几只?

结论:30只以上为佳,3的倍数 https://www.bilibili.com/video/BV17nYVekEWU/?vd_source=9b97428fe30142d2bb4f9bab6ef082a4# Import necessary libraries import numpy as np import random from collections import defaultdict import matplotlib.pyplot as plt# Function …

写一个cpp uos系统

#include <iostream>int main() {std::cout << "Hello, UOS!" << std::endl;return 0; }创建一个test.cpp文件, 在当前目录打开终端, 把上面代码内容写里面, 输入g++ test.cpp, 生成了一个a.out文件, ./a.out 输出 Hello, UOS!

C#项目—彩票选号

C#彩票选号软件 今天做了一个彩票选号的小软件,将学到的知识点总结如下; 1.写程序的思路实体类(属性、方法) 主程序调用类方法2.学到的知识点判断、循环 文件的操作(保存、打开) 集合、数组(增加-AddRange、删除-RemoveAt、排序-Reverse) 关键字:Random(随机数-Next)…

C#学习——彩票选号软件

C#彩票选号软件 今天做了一个彩票选号的小软件,将学到的知识点总结如下; 1.写程序的思路实体类(属性、方法) 主程序调用类方法2.学到的知识点判断、循环 文件的操作(保存、打开) 集合、数组(增加-AddRange、删除-RemoveAt、排序-Reverse) 关键字:Random(随机数-Next)…