feat: v2.1.0 核心重构与功能增强

## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
ZacharyZcR
2026-01-11 20:16:23 +08:00
parent 6b13b2e84f
commit 71b92d4408
948 changed files with 92335 additions and 24630 deletions
+120
View File
@@ -0,0 +1,120 @@
/*
* main.c - fscan-lite 主程序
*
* 极简的TCP内网端口扫描器
* 目标:最大兼容性,最小复杂度
*/
#include "../include/platform.h"
/* 函数声明 */
int tcp_connect_test(const char* host, int port, int timeout);
int scan_host_ports(const char* host, const int* ports, int port_count, int timeout);
int parse_ports(const char* port_str, int* ports, int max_ports);
int parse_hosts(const char* host_str, char hosts[][MAX_HOST_LEN], int max_hosts);
/* 显示版本信息 */
void show_version(void) {
printf("fscan-lite v1.0 - Lightweight TCP Port Scanner\n");
printf("Built for maximum compatibility (Windows 98 - Windows 11, Linux glibc 2.3+)\n");
printf("Copyright (c) 2024\n");
}
/* 显示使用帮助 */
void show_usage(const char* program_name) {
printf("Usage: %s [OPTIONS]\n", program_name);
printf("\n");
printf("Options:\n");
printf(" -h HOST Target host (IP address)\n");
printf(" -p PORTS Ports to scan (e.g. 80,443 or 1-1000)\n");
printf(" -t TIMEOUT Connection timeout in seconds (default: 3)\n");
printf(" --help Show this help message\n");
printf(" --version Show version information\n");
printf("\n");
printf("Examples:\n");
printf(" %s -h 192.168.1.1 -p 22,80,443\n", program_name);
printf(" %s -h 10.0.0.1 -p 1-1000 -t 2\n", program_name);
printf("\n");
}
/* 主函数 */
int main(int argc, char* argv[]) {
char* target_host = NULL;
char* port_string = NULL;
int timeout = DEFAULT_TIMEOUT;
int ports[1000]; /* 支持最多1000个端口 */
int port_count = 0;
int i;
int result;
/* 参数解析 */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 && i + 1 < argc) {
target_host = argv[++i];
}
else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
port_string = argv[++i];
}
else if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) {
timeout = atoi(argv[++i]);
if (timeout <= 0) timeout = DEFAULT_TIMEOUT;
}
else if (strcmp(argv[i], "--help") == 0) {
show_usage(argv[0]);
return 0;
}
else if (strcmp(argv[i], "--version") == 0) {
show_version();
return 0;
}
else {
printf("Unknown option: %s\n", argv[i]);
show_usage(argv[0]);
return 1;
}
}
/* 验证必需参数 */
if (!target_host) {
printf("Error: Target host (-h) is required\n");
show_usage(argv[0]);
return 1;
}
if (!port_string) {
printf("Error: Ports (-p) are required\n");
show_usage(argv[0]);
return 1;
}
/* 初始化平台 */
if (platform_init() != 0) {
printf("Error: Failed to initialize platform\n");
return 1;
}
/* 解析端口 */
port_count = parse_ports(port_string, ports, sizeof(ports) / sizeof(ports[0]));
if (port_count == 0) {
printf("Error: No valid ports specified\n");
platform_cleanup();
return 1;
}
printf("fscan-lite - Starting scan\n");
printf("Target: %s\n", target_host);
printf("Ports: %d ports to scan\n", port_count);
printf("Timeout: %d seconds\n", timeout);
printf("=================================\n");
/* 执行扫描 */
result = scan_host_ports(target_host, ports, port_count, timeout);
printf("=================================\n");
printf("Scan completed: %d open ports found\n", result);
/* 清理资源 */
platform_cleanup();
return 0;
}
+111
View File
@@ -0,0 +1,111 @@
/*
* platform.c - 平台抽象层实现
*
* 实现最基础但最可靠的平台相关功能
*/
#include "../include/platform.h"
/* 全局初始化标志 */
static int platform_initialized = 0;
/*
* 平台初始化
* Windows: 初始化Winsock
* Unix: 无需特殊初始化
*/
int platform_init(void) {
if (platform_initialized) {
return 0;
}
#ifdef PLATFORM_WINDOWS
WSADATA wsaData;
int result;
/* 初始化Winsock - 请求版本2.0,兼容Win98 */
result = WSAStartup(MAKEWORD(2, 0), &wsaData);
if (result != 0) {
/* 如果2.0失败,尝试1.1Win95/NT兼容) */
result = WSAStartup(MAKEWORD(1, 1), &wsaData);
if (result != 0) {
return -1;
}
}
#endif
platform_initialized = 1;
return 0;
}
/*
* 平台清理
*/
void platform_cleanup(void) {
if (!platform_initialized) {
return;
}
#ifdef PLATFORM_WINDOWS
WSACleanup();
#endif
platform_initialized = 0;
}
/*
* 设置socket超时
* 兼容所有平台的最可靠方法
*/
int set_socket_timeout(socket_t sock, int timeout_seconds) {
#ifdef PLATFORM_WINDOWS
DWORD timeout_ms = timeout_seconds * 1000;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
(char*)&timeout_ms, sizeof(timeout_ms)) != 0) {
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
(char*)&timeout_ms, sizeof(timeout_ms)) != 0) {
return -1;
}
#else
struct timeval tv;
tv.tv_sec = timeout_seconds;
tv.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
(void*)&tv, sizeof(tv)) != 0) {
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
(void*)&tv, sizeof(tv)) != 0) {
return -1;
}
#endif
return 0;
}
/*
* 设置socket为非阻塞模式
* 跨平台兼容实现
*/
int make_socket_nonblocking(socket_t sock) {
#ifdef PLATFORM_WINDOWS
u_long mode = 1;
return ioctlsocket(sock, FIONBIO, &mode);
#else
int flags;
flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
return fcntl(sock, F_SETFL, flags);
#endif
}
+164
View File
@@ -0,0 +1,164 @@
/*
* scanner.c - 核心TCP端口扫描实现
*
* 极简但可靠的端口扫描逻辑,专注于内网环境
*/
#include "../include/platform.h"
/*
* 基础TCP连接测试
* 返回: 1=端口开放, 0=端口关闭, -1=错误
*/
int tcp_connect_test(const char* host, int port, int timeout) {
socket_t sock;
struct sockaddr_in addr;
int result;
/* 参数验证 */
if (!host || port <= 0 || port > 65535) {
return -1;
}
/* 创建socket */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET_VALUE) {
return -1;
}
/* 设置超时 */
if (set_socket_timeout(sock, timeout) != 0) {
close_socket(sock);
return -1;
}
/* 设置目标地址 */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned short)port);
/* 转换IP地址 */
addr.sin_addr.s_addr = inet_addr(host);
if (addr.sin_addr.s_addr == INADDR_NONE) {
/* 如果不是有效IP,当作域名处理 */
struct hostent* he;
he = gethostbyname(host);
if (!he) {
close_socket(sock);
return -1;
}
memcpy(&addr.sin_addr, he->h_addr_list[0], he->h_length);
}
/* 执行连接测试 */
result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
/* 关闭socket */
close_socket(sock);
/* 返回结果 */
return (result == 0) ? 1 : 0;
}
/*
* 扫描单个主机的多个端口
*/
int scan_host_ports(const char* host, const int* ports, int port_count, int timeout) {
int i;
int open_count = 0;
if (!host || !ports || port_count <= 0) {
return 0;
}
printf("Scanning %s...\n", host);
for (i = 0; i < port_count; i++) {
int result = tcp_connect_test(host, ports[i], timeout);
if (result == 1) {
printf("%s:%d open\n", host, ports[i]);
open_count++;
} else if (result == -1) {
/* 静默处理错误,继续扫描 */
}
/* 简单的进度指示 */
if ((i + 1) % 100 == 0 || i == port_count - 1) {
printf("Progress: %d/%d ports scanned\n", i + 1, port_count);
}
}
return open_count;
}
/*
* 解析端口范围字符串
* 支持: "80", "80,443", "1-1000", "80,443,8000-8080"
*/
int parse_ports(const char* port_str, int* ports, int max_ports) {
char* str_copy;
char* token;
int count = 0;
if (!port_str || !ports || max_ports <= 0) {
return 0;
}
/* 复制字符串以便修改 */
str_copy = malloc(strlen(port_str) + 1);
if (!str_copy) {
return 0;
}
strcpy(str_copy, port_str);
/* 使用strtok(兼容性更好) */
token = strtok(str_copy, ",");
while (token && count < max_ports) {
char* dash = strchr(token, '-');
if (dash) {
/* 处理范围 "start-end" */
int start, end, i;
*dash = '\0';
start = atoi(token);
end = atoi(dash + 1);
if (start > 0 && end > 0 && start <= end && end <= 65535) {
for (i = start; i <= end && count < max_ports; i++) {
ports[count++] = i;
}
}
} else {
/* 处理单个端口 */
int port = atoi(token);
if (port > 0 && port <= 65535) {
ports[count++] = port;
}
}
token = strtok(NULL, ",");
}
free(str_copy);
return count;
}
/*
* 简单的IP范围解析
* 目前只支持单个IP,后续可扩展
*/
int parse_hosts(const char* host_str, char hosts[][MAX_HOST_LEN], int max_hosts) {
if (!host_str || !hosts || max_hosts <= 0) {
return 0;
}
/* 目前简化实现:只处理单个主机 */
if (strlen(host_str) < MAX_HOST_LEN) {
strcpy(hosts[0], host_str);
return 1;
}
return 0;
}