TCP SYN 扫描发送器(重点:构造与发送)
博客,重点讲解实现 TCP SYN 扫描时的核心步骤:构造 TCP SYN 包、构造 IP 头、发送数据包、清理 libnet 缓存。其他部分(初始化、接收响应、速率控制)也简要覆盖。
一、先决知识(快速回顾)
- C 语言:结构体、指针、内存分配、字符串处理、函数调用。
- TCP/IP 基础:IPv4 与 TCP 头部字段(源/目的端口、序列号、标志位、校验和等)、网络字节序(htons/htonl)。
- libnet:一个用来方便构造并发送原始网络包的 C 库。
- 权限:构造/发送原始包需要 root 权限。
二、为什么这些步骤是重点
实现 SYN 扫描的核心就是:你需要把正确的 TCP 头和 IP 头放到一起并把它们发出去。这四步——构造 TCP SYN、构造 IP 头、发送、清理——决定了包是否被正确发送、目标是否能正确解析、以及你的程序是否稳定、安全。
下面把每一步拆开详讲,并配以示例代码。
三、构造 TCP SYN 包(详细)
目标
构造一个有效的 TCP 头部并设置 SYN 标志,其他字段合理设置(源端口、序列号、窗口、校验和交给 libnet 计算)。
关键字段解释
- src_port:源端口(工具一般随机选择或固定为某个端口)。
- dst_port:目标端口(扫描目标)。
- seq:序列号(可以随机以避免被检测到简单特征)。
- ack:未使用(扫描时通常为 0)。
- flags:设置为- TH_SYN。
- window:窗口大小(例如 32767)。
- checksum:如果用 libnet,传 0 让它自动计算。
libnet 代码示例
libnet_build_tcp(src_port,          // 源端口dst_port,          // 目标端口seq,               // 序列号0,                 // ackTH_SYN,            // flags -> SYN32767,             // 窗口0,                 // checksum (0 means libnet calc)0,                 // urgent ptrLIBNET_TCP_H,      // tcp header lengthNULL, 0,           // payloadctx->l,            // libnet context0                  // ptag (0 -> build new)
);
实践建议
- 随机化 src_port和seq可以减少被 IDS/IPS 通过简单指纹识别。
- 不要在高速循环里构造巨量随机数而不做速率控制。
四、构造 IPv4 头(详细)
目标
为你的 TCP 段封装一个合法的 IPv4 头,使得目标能把包路由到正确的目标应用层。
关键字段解释
- tos:服务类型,通常 0。
- length:IP 报文总长度(libnet 会根据传入的 payload 自动计算或可手动传长度)。
- id:标识(可随机)。
- ttl:生存时间(常见值 64)。
- protocol:- IPPROTO_TCP。
- src_ip/- dst_ip:源/目标 IP(注意网络字节序)。
libnet 代码示例
libnet_build_ipv4(LIBNET_TCP_H + LIBNET_IPV4_H, // total length (libnet 可覆盖)0,                            // tosid,                           // id0,                            // frag64,                           // ttlIPPROTO_TCP,                  // upper layer protocol0,                            // checksum (0 -> libnet calc)src_ip,                       // source IP (in_addr_t)dst_ip,                       // dest IPNULL, 0,                      // payloadctx->l,                       // libnet context0                             // ptag
);
实践建议
- 源 IP:若你的机器有多个接口或 IP,确保选择正确的源 IP,或让内核选择(libnet 提供方法)。
- IP 标识(id)可用于区分数据包,随机或递增皆可。
五、发送数据包(详细)
目标
把构造好的 IP+TCP 包从网卡发出,确保内核/网卡能正确发送。
libnet 发送
int bytes = libnet_write(ctx->l);
if (bytes == -1) {fprintf(stderr, "libnet_write error: %s
", libnet_geterror(ctx->l));
} else {printf("sent %d bytes
", bytes);
}
实践建议
- libnet_write会把当前构造的 packet 写入链路层。
- 推荐在发送后短暂 usleep,避免以极高速率发送导致网络拥塞或触发防护。速率控制可以基于固定 sleep、令牌桶或并发数控制。
- 如果目标在本机局域网外,注意路由与 NAT 问题(返回包可能被 NAT 改写)。
六、清理 libnet 缓存(详细)
为什么需要清理
libnet 在 ctx 内部维护了构造包的缓存(ptag 等)。如果不清理,下一次构造同类头部可能复用或冲突,导致发送错误或累积内存。
libnet 清理 API
libnet_clear_packet(ctx->l);
在每次 libnet_write 之后都执行 libnet_clear_packet,或者在每次循环末尾调用。
额外释放
程序退出前调用 libnet_destroy(ctx->l) 释放所有资源。
七、把四步合并成 send_syn_packets(示例)
下面给出一个较完整的示例函数(去掉错误处理的详细分支以便初学者阅读):
void send_syn_packets(scan_context_t *ctx, scan_config_t *config) {uint32_t src_ip = libnet_name2addr4(ctx->l, "192.168.1.100", LIBNET_RESOLVE); // or use libnet_get_ipaddr4for (int i = 0; i < config->port_count; ++i) {uint16_t dst_port = config->ports[i];uint16_t src_port = (uint16_t)(1025 + (rand() % 55535));uint32_t seq = (uint32_t)rand();libnet_build_tcp(src_port, dst_port, seq, 0, TH_SYN, 32767, 0, 0, LIBNET_TCP_H, NULL, 0, ctx->l, 0);libnet_build_ipv4(LIBNET_TCP_H + LIBNET_IPV4_H, 0, (uint16_t)rand(), 0, 64, IPPROTO_TCP, 0, src_ip, libnet_name2addr4(ctx->l, config->target_ip, LIBNET_RESOLVE), NULL, 0, ctx->l, 0);int bytes = libnet_write(ctx->l);if (bytes <= 0) {fprintf(stderr, "send error: %s
", libnet_geterror(ctx->l));} else {printf("Sent SYN to %s:%u (src_port=%u)
", config->target_ip, dst_port, src_port);}libnet_clear_packet(ctx->l);usleep(1000); // 1ms 默认节流,按需调整}
}
说明:示例省略了重试、超时、并发控制以及对
libnet_build_*返回值的严格检查。真实工具建议加上这些。
八、调试与验证
- 在本地网络测试:先对你可控的内网主机做测试,避免触犯法律。
- tcpdump / wireshark:在发送端或目标端用抓包工具检查发出的包是否包含 SYN 标志以及 IP/TCP 字段是否正确。
- libpcap 捕获响应:实现一个简单的 pcap捕获模块,判断是否收到SYN+ACK/RST/ 无响应。
