最近空闲写了个端口扫描器玩具,用 pthreads 实现的 SYN 并发扫描。除了有些小问题以外,拿自己的服务器、树莓派等等测试下来效果还行。源码点这里

基本上是 Linux 的 POSIX 系统调用。为了保证超快的扫描速度,线程池写的夸张的大。理论上,如果目标的 SYN 队列不被阻塞,以太网局域网内大约 3 秒能扫出所有开放端口。实际测试,把目标的 SYN 队列阻塞前,能在大约 1 秒内扫出前 16383 的所有开放端口。

同时也是因为线程池太大,所以会有一些问题。在连接延迟低的时候并发比较夸张,线程的标准输出会出现同步问题,具体表现是扫描输出的端口号比实际小 1(有时也会大 1);同时,如果本机系统允许打开文件数过低会导致 socket 创建失败;内存吃紧的时候创建线程会失败。

比如扫描结果输出 21 端口开放,但其实有可能是 22…… 再比如输出 79,实际是 80 等等,端口号越低越有可能发生这种问题。为了确定到底是哪个端口开放,又另写了个单独的验证工具,这里直接在文后贴出源码,假设编译出目标 verify,用法 verify <地址> <端口>。两个程序配合一下,扫出开放端口还是非常快的。

如有心修改则很容易写出真正的 SYN flood 攻击工具。但这里的工具仅作实验用途,没有任何实际攻击能力,使用请遵守法律法规,请自行承担一切后果及责任,本人不为任何非法用途背书。

/* verify.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(int argc, char** argv) {
    if(argc != 3) {
        fprintf(stderr, "Usage: %s <addr> <port>\n", argv[0]);
        return 1;
    }

    struct addrinfo hints = {};
    struct addrinfo *result;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int status = getaddrinfo(argv[1], argv[2], &hints, &result);
    if(status != 0) {
        fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
        return 1;
    }

    int sockfd = socket(result->ai_family,
                        result->ai_socktype,
                        result->ai_protocol);
    int tmp = errno;
    if(sockfd == -1) {
        fprintf(stderr, "socket(): %s\n", strerror(tmp));
        return 1;
    }

    status = connect(sockfd, result->ai_addr, result->ai_addrlen);
    tmp = errno;
    if(status == -1) {
        fprintf(stderr, "connect(): %s\n", strerror(tmp));
        return 1;
    }

    freeaddrinfo(result);
    fprintf(stdout, "Connected to %s:%s\n", argv[1], argv[2]);
    return 0;
}