ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

纯C实现的轻量级NIDS:从PCAP抓包到实时告警

纯C实现的轻量级NIDS:从PCAP抓包到实时告警 简介本资源是一套基于PCAP库实现的轻量级网络入侵检测系统IDS完整源码及配套说明面向计算机、电子信息、网络安全等专业的本科生与研究生适用于课程设计、期末大作业及毕业设计实践。项目采用C语言为主开发辅以Python脚本与Shell测试工具涵盖数据包捕获sniff.c/h、协议分析analysis.c/h、多线程调度dispatch.c/h、主控逻辑main.c及ARP欺骗测试arp-poison.py结构清晰、模块解耦便于理解网络流量解析与异常检测核心流程。压缩包共17个文件含6个C源码、4个头文件、2个Makefile构建脚本、1个PDF课程文档、1个Markdown说明及Shell/Python辅助脚本等总大小889KB体积精简且开箱即用。已有354人学习下载提供可直接编译运行的工程框架、典型攻击场景测试方案及详细项目说明助读者快速掌握底层网络编程与入侵检测算法实现逻辑。1. 为什么一个.zip包里放着pcap文件、Makefile和sniff.c就能跑出真正的网络入侵检测系统这不是教学 Demo也不是课程作业压缩包——它是一套能直接在 Linux 主机上编译、抓包、实时分析 TCP/UDP 流量并触发告警的轻量级 NIDSNetwork Intrusion Detection System最小可行实现。它不依赖 Suricata 或 Snort 的庞杂规则引擎也不走 Python Scapy 的高开销路径而是用纯 C 写成的嗅探器 状态化协议解析器 规则匹配器三位一体结构从网卡原始数据链路层帧开始逐层剥离以太网头、IP 头、TCP/UDP 头提取 payload 后做字符串/正则匹配比如检测GET /etc/passwd HTTP/1.1或id;uname -a命中即打印告警到终端。适合嵌入式边缘设备、安全实验室复现经典攻击链、CTF 防御侧调试也常被用于高校《网络安全实践》课设的底层原理验证环节。如果你正在找“能看见 raw packet 到 alert 之间每一步怎么走”的源码而不是调个 API 就完事的黑匣子这个包就是你该停下的地方——它没有 Web 控制台没有数据库持久化但每一行pcap_dispatch()调用、每一个struct iphdr*强转、每一条if (strstr(payload, union select) ! NULL)都清清楚楚。新手能照着make sudo ./nids跑起来熟手能立刻定位到rules.h里改签名、在sniff.c第 217 行加日志、或把pcap_open_live()换成pcap_open_offline()做离线回放分析。2. 从解压到告警三步跑通基于 PCAP 的 C 语言 NIDS这个.zip包不是“下载即用”它本质是一个需要本地编译环境支撑的源码工程。它的生命力不在预编译二进制而在你亲手敲下make后看到gcc把sniff.c、parser.c、rules.c编译链接成可执行文件那一刻——这才是理解网络入侵检测底层逻辑的真正起点。下面步骤严格按真实编译链路展开不跳过任何隐含依赖。2.1 解压后先看懂目录结构和 Makefile 的真实意图解压后你会看到类似这样的文件树nids-project/ ├── Makefile ├── sniff.c # 主程序pcap 初始化、循环捕获、分发包给解析器 ├── parser.c # 协议解析eth → ip → tcp/udp → payload 提取 ├── rules.c / rules.h # 规则定义与匹配硬编码的攻击特征字符串数组 ├── util.c # 工具函数hexdump、ip_to_str、payload_sanitizer └── test.pcap # 自带测试流量含 SQLi、XSS、目录遍历等典型 payload注意Makefile不是装饰品。它明确声明了-lpcap链接 libpcap指定了-O2优化等级并把CFLAGS设为-Wall -stdc99 -I.—— 这意味着它默认不兼容 C且所有头文件必须放在当前目录-I.。如果你看到fatal error: pcap.h: No such file or directory问题不在代码而在没装libpcap-devUbuntu/Debian或libpcap-develCentOS/RHEL。2.2 用标准 Linux 工具链完成编译四条命令覆盖 95% 场景以下命令在 Ubuntu 22.04 / CentOS 8 / Debian 11 上实测通过无需 root 权限编译仅运行时需sudo# 步骤 1安装 libpcap 开发库关键否则连 pcap.h 都找不到 sudo apt update sudo apt install -y libpcap-dev build-essential # Ubuntu/Debian # 或 sudo yum install -y libpcap-devel gcc make # CentOS/RHEL # 步骤 2进入项目目录确认 Makefile 存在且可读 cd nids-project ls -l Makefile sniff.c parser.c # 步骤 3执行 make —— 它会自动调用 gcc生成名为 nids 的可执行文件 make # 步骤 4验证编译结果检查符号表是否含 pcap_ 函数 file nids nm nids | grep pcap_open_live编译成功后nids文件大小通常在 15–25 KB静态链接未开启时ldd nids应显示依赖libpcap.so和libc.so。若make报错make: *** No targets. Stop.说明Makefile里没写默认 target常见于作者只写了all:但没设为 first rule此时手动运行make all或直接gcc -o nids sniff.c parser.c rules.c util.c -lpcap -Wall即可绕过。2.3 两种运行模式实时嗅探 vs 离线回放参数含义必须吃透编译成功后./nids默认行为是实时监听eth0或第一个可用网卡。但实际使用中你几乎不会直接sudo ./nids—— 因为缺少控制参数会导致误报爆炸或漏报严重。以下是两个最常用、最安全的启动方式① 离线回放测试包推荐新手首选用自带test.pcap验证规则是否生效不碰真实网卡无权限风险sudo ./nids -r test.pcap -v-r test.pcap指定离线 pcap 文件路径pcap_open_offline()加载-v启用 verbose 模式每匹配一条规则输出完整 packet hexdump payload 字符串输出示例[ALERT] SQLi detected in TCP payload: SELECT * FROM users WHERE id1 OR 11--② 实时监听指定网卡生产/实验环境必须指定网卡名避免pcap_open_live()自动选错接口如选成 lo 导致抓不到外网流量sudo ./nids -i eth0 -t 30 -q-i eth0强制监听eth0接口替换成你的实际网卡名用ip link show查-t 30设置超时 30 秒后自动退出防止无限抓包占满内存-qquiet 模式只输出告警不打印 debug 日志关键逻辑说明sniff.c中pcap_loop()的回调函数packet_handler()是核心。它接收const u_char* packet先交给parse_ethernet()解析链路层再层层调用parse_ip()→parse_tcp()→extract_payload()最后传给match_rules()扫描 payload。整个流程无内存分配全栈变量固定 buffer所以性能极高——这也是它能在 Raspberry Pi 4 上跑出 800 Mbps 抓包吞吐的底层原因。3. 规则怎么写payload 提取边界在哪三个硬核参数决定检出率这套系统的检测能力不靠机器学习而靠rules.h里明文定义的字符串签名。但“简单字符串匹配”不等于“随便写个关键词就行”。真实流量中 payload 被分片、编码、混淆、加密稍不注意就漏报。必须理解parser.c如何提取 payload以及rules.c如何做匹配才能写出有效规则。3.1 payload 提取的三大限制TCP 重组、HTTP 解码、长度截断parser.c中extract_payload()函数返回的是原始 TCP segment payload不是应用层完整请求体。这意味着限制类型具体表现对规则的影响TCP 重组缺失若攻击 payload 跨多个 TCP segment如大文件上传当前代码只检查单个 segment payload无法拼接完整请求规则必须匹配单 segment 内可见特征如?php system(在首段而非); ?在末段HTTP 解码未做URL 编码%3Cscript%3E、Base64PHNjcmlwdD4等未解码直接匹配规则需写编码后形式或自行在match_rules()前加解码逻辑长度硬截断payload_len最大设为MAX_PAYLOAD_LEN 1024见config.h超长部分丢弃SQLi 中长UNION SELECT语句若超过 1024 字节后半段不可见实操建议打开config.h把#define MAX_PAYLOAD_LEN 1024改为2048重新make。但注意增大此值会提高内存占用且对性能影响呈线性增长实测从 1024→2048Raspberry Pi 4 CPU 占用率从 12% 升至 28%。3.2 规则定义语法RULE宏背后的 C 预处理真相rules.h通常长这样// rules.h #define RULE(name, pattern, desc) { .name name, .pattern pattern, .desc desc } static rule_t rules[] { RULE(SQLI_UNION, union select, Classic SQL injection union-based), RULE(XSS_SCRIPT, script, Cross-site scripting attempt), RULE(PATH_TRAV, ../etc/passwd, Directory traversal attempt), };这里RULE是个宏展开后生成rule_t结构体数组。每个规则本质是patternC 字符串字面量区分大小写不支持正则除非你把strstr()换成regexec()desc纯描述不影响匹配逻辑匹配函数match_rules()内部调用strstr(payload, r-pattern) ! NULL血泪经验不要写UNION SELECT大写因为 HTTP payload 绝大多数是小写。也不要写../passwd—— 真实攻击中常是....//etc/passwd四个点绕过双点过滤必须覆盖变体。我一般会加一条RULE(PATH_TRAV_ALT, ....//etc/passwd, ...)。3.3 三条必调参数MIN_PAYLOAD_LEN、TCP_ONLY、IGNORE_CASE这些参数藏在config.h里直接影响检出率和误报率必须根据场景调整参数名默认值作用调整建议MIN_PAYLOAD_LEN10小于该长度的 payload 直接跳过匹配攻击 payload 通常 20 字节设为20可过滤大量 ACK 包噪声TCP_ONLY1是否只分析 TCP 流量UDP 被忽略若需检测 DNS 隧道或 UDP Flood设为0并在parse_udp()中加 payload 提取IGNORE_CASE0strstr()是否忽略大小写设为1需改用strcasestr()POSIX.1-2008Ubuntu 22.04 原生支持CentOS 7 需自己实现修改后务必make clean make重建否则旧二进制仍用旧参数。4. 编译失败、抓不到包、规则不触发五个真实踩坑记录与现场排查法这套系统看似简单但因深度绑定 libpcap 和内核网络栈90% 的问题都出在环境适配而非代码逻辑。以下是我在三所高校实验室、两个 SOC 团队部署时遇到的高频问题按「现象 → 原因 → 解决」还原现场。4.1 现象make报错undefined reference to pcap_open_live原因Makefile中-lpcap写在了sniff.o之后而链接器要求库名必须在目标文件之后GNU ld 规则。常见于作者复制粘贴错误的 Makefile。解决打开Makefile找到LDFLAGS行确保-lpcap在所有.o文件之后。正确写法nids: sniff.o parser.o rules.o util.o gcc -o $ $^ -lpcap -Wall # ← -lpcap 必须在 $^所有 .o之后4.2 现象sudo ./nids -i eth0启动后无任何输出tcpdump -i eth0 port 80却能看到 HTTP 流量原因eth0接口处于PROMISC模式关闭状态且目标主机启用了rp_filter反向路径过滤导致pcap无法捕获非本机目的 IP 的包即使你在混杂模式下。解决# 临时关闭 rp_filter立即生效 echo 0 | sudo tee /proc/sys/net/ipv4/conf/eth0/rp_filter # 永久关闭写入 sysctl.conf echo net.ipv4.conf.eth0.rp_filter 0 | sudo tee -a /etc/sysctl.conf sudo sysctl -p # 确认混杂模式已开 sudo ip link set eth0 promisc on4.3 现象离线回放test.pcap时[ALERT]一条不出现但verbose模式显示 payload 为空原因test.pcap是从 Windows 用 Wireshark 抓的链路层封装为PPP或IEEE 802.11而parse_ethernet()只处理DLT_EN10MB以太网。pcap_datalink()返回值不匹配导致解析函数直接 return。解决用tshark转换封装类型tshark -r test.pcap -w test_eth.pcap -F pcap -T fields -e frame.protocols # 查看原始 link type tshark -r test.pcap -w test_eth.pcap -F pcap -y EN10MB # 强制转为以太网封装4.4 现象规则admin --能匹配但admin#MySQL 注释符不触发原因#是 URL 编码中的特殊字符在 HTTP GET 请求中会被编码为%23而parser.c未做 URL decode。原始 payload 是admin%23自然匹配不上admin#.解决在extract_payload()返回前插入简易 URL decode只需处理%XX// 在 parser.c 中 extract_payload() 末尾添加 char* url_decode(char* s) { char* p s; while (*p) { if (*p % isxdigit(p[1]) isxdigit(p[2])) { char hex[3] {p[1], p[2], \0}; *s (char)strtol(hex, NULL, 16); p 3; } else { *s *p; } s; } *s \0; return s; }然后在match_rules()前调用url_decode(payload)。4.5 现象sudo ./nids -r test.pcap运行几秒后崩溃dmesg显示segfault at 0000000000000010原因test.pcap中存在 malformed packet如 IP header length 20parse_ip()中iph-ihl * 4计算出负偏移导致后续memcpy()访问非法地址。解决在parse_ip()开头加健壮性检查if (iph-ihl 5 || iph-ihl 15) { return NULL; // IPv4 header 至少 20 字节ihl5最多 60 字节ihl15 } int ip_header_len iph-ihl * 4; if (ip_header_len len) return NULL; // 防止越界读5. 从检测到响应给原始 NIDS 加上 TCP Reset 注入能力实战级增强光有告警不够——真正的入侵检测系统必须能主动阻断。Linux 下最轻量、最可控的方式是发送伪造 TCP RST 包让攻击连接立即中断。这不需要 iptables 或 netfilter纯用户态构造 IP/TCP 包即可实现且与现有sniff.c架构无缝集成。我把它做成一个独立函数send_tcp_rst()插在packet_handler()命中规则后的分支里。5.1 构造 RST 包的三要素源/目的 IP、源/目的端口、序列号TCP RST 包的关键在于序列号seq必须等于对方下一个期望接收的 seq否则被丢弃。我们无法获取 TCP 连接状态但可以利用ACK包中的ack字段——当收到ACK1000时对方期望的下一个 seq 就是1000此时发seq1000, rst1即可重置连接。// 在 util.c 中添加 #include netinet/ip.h #include netinet/tcp.h #include net/if.h #include sys/ioctl.h void send_tcp_rst(uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, uint32_t ack_seq) { int sock socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sock 0) return; struct iphdr* ip malloc(40); // max IP header struct tcphdr* tcp (struct tcphdr*)(ip 1); // IP header ip-ihl 5; ip-version 4; ip-tos 0; ip-tot_len htons(sizeof(struct iphdr) sizeof(struct tcphdr)); ip-id htons(12345); ip-frag_off 0; ip-ttl 64; ip-protocol IPPROTO_TCP; ip-check 0; ip-saddr src_ip; ip-daddr dst_ip; ip-check csum((unsigned short*)ip, sizeof(struct iphdr)); // TCP header tcp-source src_port; tcp-dest dst_port; tcp-seq htonl(ack_seq); // ← 关键用对方的 ack 作为我们的 seq tcp-ack_seq 0; tcp-doff 5; tcp-fin 0; tcp-syn 0; tcp-rst 1; // ← 核心标志位 tcp-psh 0; tcp-ack 0; tcp-urg 0; tcp-window htons(0); tcp-check 0; tcp-urg_ptr 0; tcp-check tcp_csum(ip-saddr, ip-daddr, tcp, sizeof(struct tcphdr)); struct sockaddr_in sin; sin.sin_family AF_INET; sin.sin_addr.s_addr dst_ip; sendto(sock, ip, sizeof(struct iphdr)sizeof(struct tcphdr), 0, (struct sockaddr*)sin, sizeof(sin)); close(sock); free(ip); }参数说明src_ip/dst_ip从原始 packet 的 IP header 中提取src_port/dst_port从 TCP header 提取ack_seq从 TCP header 的ack_seq字段获取注意字节序转换ntohl()。csum()和tcp_csum()是标准校验和函数网上可搜到实现此处省略。5.2 在packet_handler()中注入 RST精准阻断而非泛滥攻击修改sniff.c中的回调函数在match_rules()返回 true 后立即调用// sniff.c 中 packet_handler() 片段 if (match_rules(payload, payload_len)) { printf([ALERT] %s from %s:%d to %s:%d\n, matched_rule-desc, ip_to_str(iph-saddr), ntohs(tcph-source), ip_to_str(iph-daddr), ntohs(tcph-dest)); // ← 新增只对 TCP 连接发 RST且只发一次避免风暴 static uint64_t last_rst_time 0; if (time(NULL) - last_rst_time 1) { // 1 秒内限发一次 send_tcp_rst(iph-daddr, iph-saddr, tcph-dest, tcph-source, ntohl(tcph-seq) payload_len); last_rst_time time(NULL); } }关键细节RST 的seq设为ntohl(tcph-seq) payload_len因为攻击 payload 属于当前 segment对方下一个期望 seq 就是当前 seq payload 长度。tcph-seq是网络字节序必须ntohl()转为主机序再加。5.3 验证 RST 效果用 curl 和 Wireshark 双向确认部署后用另一台机器执行攻击命令curl http://target-ip/?id1%20UNION%20SELECT%201,2,3预期现象nids终端立即打印[ALERT] SQLi detected...并发送 RST攻击机curl报错curl: (56) Recv failure: Connection reset by peerWireshark 抓包可见目标机发出一个TCP RST包源端口80目的端口随机seqxxx等于 curl 的ack值我的习惯上线前必做三件事——① 用test.pcap回放验证 RST 发送逻辑② 在虚拟机里用nc -lvp 8080模拟服务端用curl http://localhost:8080/?xssscript触发③tcpdump -i any tcp[tcpflags] (tcp-rst) ! 0抓 RST 包确认数量与预期一致。这比看日志可靠十倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表