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的版本号为4
uint8_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位),通常设为0
doff : 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位),通常设为0
uint16_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;
}

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

运用Npcap库实现SYN半开放扫描的更多相关文章

  1. Python 半开放socket

    利用shutdown实现半开放的socket #server.py import socket s=socket.socket() s.bind(('127.0.0.1',2000)) s.liste ...

  2. TCP,SYN,FIN扫描

    1.TCP扫描相对来说是速度比较慢的一种,为什么会慢呢?因为这种方法在扫描的时候会从本地主机的一个端口向目标主机的一个端口发出一个连接请求报文段,而目标主机在收到这个这个请求报文后: 有回复: 若同意 ...

  3. Linux常用网络工具:批量主机服务扫描之nmap

    Linux下有很多强大网络扫描工具,网络扫描工具可以分为:主机扫描.主机服务扫描.路由扫描等. 之前已经写过常用的主机扫描和路由扫描工具,nmap支持批量主机扫描和主机服务扫描. nmap的安装直接使 ...

  4. 【无线安全实践入门】网络扫描和ARP欺骗

    文中可能存在错误操作或错误理解,望大家不吝指正. 同时也希望可以帮助到想要学习接触此方面.或兴趣使然的你,让你有个大概的印象. !阅前须知! 本文是基于我几年前的一本笔记本,上面记录了我学习网络基础时 ...

  5. 2.Metasploit数据库配置及扫描模块介绍

    01.Metasploit数据库配置及扫描模块介绍     信息收集   信息收集是渗透测试中首先要做的重要事项之一,目的是尽可能多的查找关于目标的信息,我们掌握的信息越多,渗透成功的机会越大.在信息 ...

  6. Nmap 常用命令语法

    Nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端,确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统,正如大多数被用于网络安全的工具,Nmap也是不少黑客及骇客爱用的工具, ...

  7. Nmap命令的常用实例

    一.Nmap简介 nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称 fingerprinting).它是网络管 ...

  8. 不老的神器:安全扫描器Nmap渗透使用指南【转】

    介绍 nmap是用来探测计算机网络上的主机和服务的一种安全扫描器.为了绘制网络拓扑图Nmap的发送特制的数据包到目标主机然后对返回数据包进行分析.Nmap是一款枚举和测试网络的强大工具. 特点 主机探 ...

  9. kali视频学习(6-10)

    第三周 kali视频(6-10)学习 6.信息搜集之主机探测 7.信息搜集之主机扫描(nmap使用) 8.信息搜集之指纹识别 9.信息搜集之协议分析 10.漏洞分析之OpenVAS安装 6.信息搜集之 ...

  10. 转-Nmap扫描原理与用法

    1     Nmap介绍 操作系统与设备类型等信息. Nmap的优点: 1.      灵活.支持数十种不同的扫描方式,支持多种目标对象的扫描. 2.      强大.Nmap可以用于扫描互联网上大规 ...

随机推荐

  1. TXT文本文件存储

    用解析器解析出数据之后,接下来就是存储数据了.保存的形式可以多种多样,最简单的形式是直接保存为文本文件,如 TXT.JSON.CSV 等.另外,还可以保存到数据库中,如关系型数据库 MySQL,非关系 ...

  2. Chrome插件:​Vue.js Devtools 高效地开发和调试

    ​ 在现代前端开发中,Vue.js因其灵活性和性能优势,受到越来越多开发者的青睐.然而,随着项目规模的扩大,调试和优化变得愈发复杂.幸运的是,Vue.js Devtools的出现,为开发者提供了一套强 ...

  3. Linux 内核:设备树 学习总结

    背景 之前写过设备树DTS 学习:学习总结(应用篇)的学习,但是是偏向于应用:这次针对了设备树的架构以及在驱动中的使用流程做了补充. 基于 Linux 内核 v4.14. 目录 标题 说明 设备树:d ...

  4. 设备树DTS 学习:学习总结(应用篇)

    设备树DTS 学习:学习总结(应用篇) 背景 经过前几章的学习,我们可以说是掌握了设备树的基础用法,现在作为总结回顾. 1.设备树DTS 学习:有关概念 介绍了什么是设备树,设备树的作用,如何编译设备 ...

  5. Kubernetes 存储资源 PV、PVC 和StorageClass详解

    一.存储机制介绍 在 Kubernetes 中,存储资源和计算资源(CPU.Memory)同样重要,Kubernetes 为了能让管理员方便管理集群中的存储资源,同时也为了让使用者使用存储更加方便,所 ...

  6. Java JVM——11. 执行引擎

    1.概述 执行引擎属于JVM的下层,里面包括:解释器.即时编译器.垃圾回收器. 执行引擎是Java虚拟机核心的组成部分之一."虚拟机"是一个相对于"物理机"的概 ...

  7. 全国产RK3568J + FPGA的PCIe、FSPI通信实测数据分享!

    测试数据汇总 案例 时钟频率 理论速率 测试结果 FSPI通信案例 150MHz 71.53MB/s 读速率:67.452MB/s 写速率:52.638MB/s PCIe通信案例 100MHz 803 ...

  8. .Net Core 访问 appsettings.json

    1.添加 NuGet 包 Microsoft.Extensions.Configuration 2.通过注入获取 Configuration 注意:注入获取的必须提前在 StartUp 里面提前注册 ...

  9. IstioCon 回顾 | 网易数帆的 Istio 推送性能优化经验

    在 IstioCon2022 上,网易数帆资深架构师方志恒从企业生产落地实践的视角分享了多年 Istio 实践经验,介绍了 Istio 数据模型,xDS 和 Istio 推送的关系,网易数帆遇到的性能 ...

  10. Django日期字段默认值default=timezone.now

    如果你确实希望默认值是当前日期和时间,Django 提供了一个方便的函数 django.utils.timezone.now 来实现这一目的. 你可以这样调整你的模型,以使用当前日期和时间作为默认值: ...