随着Wi-Fi技术的普及,802.11协议构建的无线网络已成为现代通信的核心载体。但无线信号的广播特性,也使得其数据传输过程可能被第三方捕获——这既是网络安全审计的重要手段,也潜藏着隐私泄露风险。在众多无线嗅探工具中,80211_sniff以其针对无线环境的优化设计,成为分析无线网络密码与流量的实用工具。它基于dsniff的解码框架,专门解决了无线场景中数据包丢失导致的解析失效问题,为理解无线网络通信安全提供了独特视角。

一、802.11无线网络嗅探的基础原理

要理解80211_sniff的工作机制,首先需要掌握无线网络嗅探的底层逻辑。与有线网络不同,无线设备通过无线电波传输数据,信号覆盖范围内的所有设备都能"听"到传输内容,这为嗅探提供了物理基础。

1.1 无线通信的核心概念

  • BSSID与SSID:BSSID(基本服务集标识符)是无线接入点(AP)的MAC地址,用于标识一个特定的无线网络节点;SSID(服务集标识符)则是无线网络的名称(如"TP-LINK_1234"),用于用户识别网络。在多AP覆盖场景中,BSSID是区分不同节点的关键。

  • 802.11帧类型:无线数据传输以"帧"为单位,主要分为三类:

    • 管理帧:用于设备关联、认证、断开连接等控制逻辑(如认证请求/响应帧);
    • 控制帧:协调数据传输顺序(如确认帧、请求发送帧);
    • 数据帧:承载实际通信内容(如网页请求、文件传输数据)。
      密码嗅探的核心目标是数据帧中包含的明文或弱加密凭证,以及管理帧中的认证信息。

1.2 嗅探的技术前提:混杂模式与数据包捕获

无线网卡默认只接收目标MAC地址为自身的帧,而"混杂模式"(Promiscuous Mode)可让网卡接收所有经过的无线帧,无论目标是否为自身——这是嗅探工具的基础功能。80211_sniff通过-p选项控制是否启用混杂模式,默认开启以最大化捕获范围。

数据包捕获依赖底层库实现,80211_sniff采用了主流的libpcap库(代码中pcap_looppcap_stats等函数),该库能与操作系统内核交互,直接从网卡驱动获取原始帧数据,支持实时捕获与离线文件解析(如-r选项读取Kismet dump文件)。

二、密码嗅探的核心逻辑:从捕获到解析

无线网络密码嗅探的本质是捕获包含认证信息或明文凭证的数据包,并从中提取关键信息。其有效性取决于两个因素:目标协议的安全性,以及数据包的完整性。

2.1 目标协议:为何明文与弱加密易被嗅探?

早期网络协议(如HTTP、FTP、Telnet)采用明文传输用户名和密码,例如FTP的USERPASS命令、HTTP的Basic认证,这些数据一旦被捕获即可直接读取。即使是现代网络,部分设备仍可能使用此类不安全协议(如老旧摄像头、打印机)。

对于加密协议,嗅探难度取决于加密强度:

  • WEP加密:因密钥管理缺陷,可通过捕获足够多的初始化向量(IV)破解;
  • WPA/WPA2:需捕获"四次握手"认证帧,结合字典攻击破解;
  • WPA3:采用更安全的密钥协商机制,显著提升了嗅探难度。

80211_sniff的解码逻辑(基于dsniff的解码程序)正是针对这些协议特性,重点扫描包含凭证的数据包。

2.2 无线环境的特殊挑战:数据包丢失与80211_sniff的优化

有线网络中,数据包传输路径稳定,丢失率低,工具(如dsniff)可通过重组TCP流还原完整通信内容。但无线环境中,信号干扰、距离衰减等因素导致数据包丢失频繁,TCP流重组极易失败(dsniff的痛点)。

80211_sniff的设计突破正在于此:它放弃依赖完整TCP流,改为对每个数据包独立扫描。通过-A选项,强制每个包被所有解码程序检查,即使部分包丢失,只要包含关键凭证(如一个完整的FTP密码帧),仍能成功提取。这一设计使其特别适合无线场景。

三、80211_sniff的设计思路:针对无线场景的功能优化

80211_sniff的代码虽短,但功能设计紧密围绕无线嗅探的实际需求,体现了"实用优先"的思路。

...
struct scanfunc tcp_funcs[] =
{
	{ 5190, decode_aim, "aim"},
	{ 9898, decode_aim, "aim"},
	{ 1494, decode_citrix, "citrix"},
	{ 2401, decode_cvs, "cvs"},
	{ 21, decode_ftp, "ftp"},
	{ 2121, decode_ftp, "ftp"},
//	{ , decode_hex, "hex"},
	{ 80, decode_http, "http"},
	{ 98, decode_http, "http"},
	{ 3128, decode_http, "http"},
	{ 8080, decode_http, "http"},
	{ 8000, decode_http, "http"},
	{ 4000, decode_icq, "icq"},
	{ 143, decode_imap, "imap"},
	{ 220, decode_imap, "imap"},
	{ 6667, decode_irc, "irc"},
	{ 6668, decode_irc, "irc"},
	{ 6669, decode_irc, "irc"},
	{ 389, decode_ldap, "ldap"},
	{ 417, decode_mmxp, "mmxp/tcp"},
	{ 2417, decode_mmxp, "mmxp/tcp"},
	{ 4444, decode_napster, "napster"},
	{ 5555, decode_napster, "napster"},
	{ 6666, decode_napster, "napster"},
	{ 7777, decode_napster, "napster"},
	{ 8888, decode_napster, "napster"},
	{ 119, decode_nntp, "nntp"},
	{ 1521, decode_oracle, "oracle"},
	{ 1526, decode_oracle, "oracle"},
	{ 5631, decode_pcanywhere, "pcanywhere"},
	{ 65301, decode_pcanywhere, "pcanywhere"},
	{ 106, decode_pop, "pop"},
	{ 109, decode_pop, "pop"},
	{ 110, decode_pop, "pop"},
	{ 5432, decode_postgresql, "postgresql"},
	{ 512, decode_rlogin, "rlogin"},
	{ 513, decode_rlogin, "rlogin"},
	{ 514, decode_rlogin, "rlogin"},
	{ 139, decode_smb, "smb"},
	{ 24, decode_smtp, "smtp"},
	{ 25, decode_smtp, "smtp"},
	{ 587, decode_smtp, "smtp"},
	{ 1080, decode_socks, "socks"},
	{ 1433, decode_tds, "tds/udp"},
	{ 2638, decode_tds, "tds/udp"},
	{ 7599, decode_tds, "tds/udp"},
	{ 23, decode_telnet, "telnet"},
	{ 261, decode_telnet, "telnet"},
	{ 6000, decode_x11, "x11"},
	{ 6001, decode_x11, "x11"},
	{ 6002, decode_x11, "x11"},
	{ 6003, decode_x11, "x11"},
	{ 6004, decode_x11, "x11"},
	{ 6005, decode_x11, "x11"},
	{-1, NULL}
};

struct scanfunc udp_funcs[] =
{
	{ 417, decode_mmxp, "mmxp/udp"},
	{ 2417, decode_mmxp, "mmxp/udp"},
	{ 520, decode_rip, "rip"},
	{ 2001, decode_sniffer, "sniffer"},
	{ 161, decode_snmp, "snmp"},
	{ 1433, decode_tds, "tds/udp"},
	{-1, NULL}	
};

struct scanfunc rpc_funcs[] =
{
	{ 100005, decode_mountd, "mountd"},
	{ 100004, decode_ypserv, "ypserv"},
	{ 100009, decode_yppasswd, "yppasswd"},
	{-1, NULL}
};

struct scanfunc ip_funcs[] =
{
	{ 89, decode_ospf, "ospf"},
	{ 47, decode_pptp, "pptp"},
	{ 112, decode_vrrp, "vrrp"},
	{-1, NULL}
};

...

int scan_tcp(time_t ts, Wlan_nfo *wi, IPv4_hdr *iph, 
		TCP_hdr *tcph, u_int8_t *payload, size_t paylen)
{
...

	for (i=0; tcp_funcs[i].func != NULL; i++) {	

		if ((opt.scan_all == 0) && (tcph->tcp_dprt != ntohs(tcp_funcs[i].port)))
			continue;

		memcpy(pbuf, payload, paylen);
		
		if (tcp_funcs[i].func(pbuf, paylen, buf, sizeof(buf)-1) != 0) {
		
			print_banner(ts, wi, tcp_funcs[i].name, iph->ip_sadd, 
				tcph->tcp_sprt, iph->ip_dadd, tcph->tcp_dprt);

			len = strlen(buf);
			if ((len >= 1) && (buf[len-1] == '\n'))
				buf[len-1] = '\0';	
			if ((len >= 2) && (buf[len-2] == '\r'))
				buf[len-2] = '\0';	
			
            printf("%s\n\n", buf);
			if (opt.logf) {
				fprintf(opt.logf, "%s\n\n", buf);
				fflush(opt.logf);
			}
			decoded++;
		}
		else if (opt.traffic) {
			
			if (opt.logf)
				fprintf(opt.logf, "** TCP Traffic ");
			printf("** TCP Traffic ");
			print_banner(ts, wi, tcp_funcs[i].name, iph->ip_sadd,
				tcph->tcp_sprt, iph->ip_dadd, tcph->tcp_dprt);
		}
	}

	opt.decoded += decoded;
	return(decoded);
}

int scan_udp(time_t ts, Wlan_nfo *wi, IPv4_hdr *iph,
        UDP_hdr *udph, u_int8_t *payload, size_t paylen)
{
...
    for (i=0; udp_funcs[i].func != NULL; i++) {

        if ((opt.scan_all == 0) && (udph->udp_dprt != ntohs(udp_funcs[i].port)))
            continue;
...

        if (udp_funcs[i].func(pbuf, paylen, buf, sizeof(buf)-1) != 0) {

            print_banner(ts, wi, udp_funcs[i].name, iph->ip_sadd,
                udph->udp_sprt, iph->ip_dadd, udph->udp_dprt);

            len = strlen(buf);
            if ((len >= 1) && (buf[len-1] == '\n'))
                buf[len-1] = '\0';
            if ((len >= 2) && (buf[len-2] == '\r'))
                buf[len-2] = '\0';

            printf("%s\n\n", buf);
            if (opt.logf) {
                fprintf(opt.logf, "%s\n\n", buf);
                fflush(opt.logf);
            }
            decoded++;
        }
        else if (opt.traffic) {

            if (opt.logf)
                fprintf(opt.logf, "** UDP Traffic ");
            printf("** UDP Traffic ");
            print_banner(ts, wi, udp_funcs[i].name, iph->ip_sadd,
                udph->udp_sprt, iph->ip_dadd, udph->udp_dprt);
        }
    }

    opt.decoded += decoded;
    return(decoded);
}

...
static void print_stats(void)
{
	sig_print_stats(0);
}

static void sig_print_stats(int signo)
{
	struct pcap_stat ps;
	memset(&ps, 0x00, sizeof(ps));
	
	vprint(1, "[%s] Capture finished\n", currtime(time(NULL)));

	if (pcap_stats(p, &ps) != 0) {
		errx("Failed to retrieve pcap stats: %s\n", pcap_geterr(p));
		return;
	}
	printf("\nRunning for %u seconds\n", (u_int)(time(NULL) - start_time));
	printf("%u packets received\n", ps.ps_recv);
	printf("%u packets dropped\n", ps.ps_drop);
	printf("%u packets sucessfully decoded\n", opt.decoded);

	if (opt.logf) {
		fprintf(opt.logf, "\nRunning for %u seconds\n", (u_int)(time(NULL) - start_time));
		fprintf(opt.logf, "%u packets received\n", ps.ps_recv);
		fprintf(opt.logf, "%u packets dropped\n", ps.ps_drop);
		fprintf(opt.logf, "%u packets sucessfully decoded\n", opt.decoded);
	}

	if (opt.dumpfile)
		pcap_dump_close(opt.pdt);
	_exit(signo);
}


int main(int argc, char *argv[])
{
...

	while ( (i = getopt(argc, argv, "Avhi:r:npw:tDo:be")) != -1) {
		switch (i) {
			case 'A': opt.scan_all = 1; break;
			case 'b': opt.nobssid = 1; break;
			case 't': opt.traffic = 1; break;
			case 'p': opt.promisc = 0; break;
			case 'n': opt.resolve = 0; break;
			case 'o': logfile = optarg; break;
			case 'v': opt.verbose++; break;
			case 'r': opt.iface = optarg; break;
			case 'i': opt.iface = optarg; break;
			case 'w': 
				opt.dumpfile = optarg; 
				if (is_reg_file(opt.dumpfile)) {
					printf("File '%s' exist, overwrite? [y/n]: ", opt.dumpfile);
					if (getchar() != 'y')
						exit(EXIT_FAILURE);
				}
				break;
			
			case 'D': opt.daemon = 1; break;
			default: 
				usage(argv[0]); 
				exit(EXIT_FAILURE);
		}
	}

	argc -= optind;
	argv += optind;

	if (argc > 0)
		filter = str_join(" ", argv);

	if ( (logfile != NULL) && (opt.logf = fopen(logfile, "a")) == NULL) 
		errx("Failed to open %s: %s\n", logfile, strerror(errno));

	if (opt.daemon == 1) {
		int i = 0;
		
		if ((opt.logf == NULL) && (opt.dumpfile == NULL))
			errx("Either dumpfile (-w) or logfile (-o) must be set when running as daemon\n");
	
		if (daemonize() != 0)
			exit(EXIT_FAILURE);
		printf("[%d]\n", getpid());

		if (opt.logf == NULL) {
			if ( (opt.logf = fopen("/dev/null", "w")) == NULL)
				errx("Error opening /dev/null: %s\n", strerror(errno));
		}

		for (i=0; i<fileno(opt.logf); i++)
			close(i);

		dup2(fileno(opt.logf), fileno(stdout));
		dup2(fileno(opt.logf), fileno(stderr));
		opt.logf = NULL;
	}

	if ( (cap = cap_open(opt.iface, 1)) == NULL)
		errx("Failed to open input, bailing out\n");
	printf("[%s] Opened '%s'\n", currtime(time(NULL)), cap->dev);
	
	if (opt.logf)
		fprintf(opt.logf, "[%s] Opened '%s'\n", currtime(time(NULL)), cap->dev);

	if (filter != NULL) {
		if (cap_setfilter(cap, filter) < 0)
			exit(EXIT_FAILURE);
	
		vprint(1, "Using filter '%s'\n", filter);
		free(filter);
	}
			

	if (opt.verbose || opt.daemon) {
		p = cap->cap_pcapd;
		atexit(print_stats);
		signal(SIGTERM, sig_print_stats);
		signal(SIGINT, sig_print_stats);
		start_time = time(NULL);
	}

	if (opt.dumpfile != NULL) {
		if ( (opt.pdt = pcap_dump_open(cap->cap_pcapd, opt.dumpfile)) == NULL)
			errx("pcap_dump_open: %s\n", pcap_geterr(cap->cap_pcapd));
	}
		
	pcap_loop(cap->cap_pcapd, -1, scanpkt, (u_char *)cap);
	if (opt.dumpfile)
		pcap_dump_close(opt.pdt);
	exit(EXIT_SUCCESS);
}

典型工作流程:
(1) 先用 Kismet/airodump-ng 长时间抓包,生成 .pcap;
(2) 80211_sniff -r kismet-20250720.pcap -o pw.log -t -v
(3) 查看 pw.log,发现

14:23:10 BSSID:AA:BB:CC:DD:EE:FF 192.168.0.123 → 210.22.70.123 FTP user:admin pass:P@ssw0rd!

即可精准定位到“哪个 AP、哪台终端”泄露了口令。

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

3.1 适配无线环境的核心功能

  • BSSID识别:无线环境中,多个AP可能使用相同网段(如192.168.0.x),仅靠IP地址难以区分设备归属。80211_sniff默认打印BSSID(可通过-b关闭),结合Kismet dump文件分析时,能快速定位某个AP下的不安全设备。

  • 离线与实时双模支持:除实时捕获(-i指定接口),还支持读取离线文件(-r),这对分析历史数据(如Kismet记录的攻击痕迹)至关重要。

  • 流量检测与日志-t选项启用流量检测,即使数据包未被成功解码,只要目标是已知不安全端口(如21/FTP、23/Telnet),仍会触发提醒,帮助发现潜在风险。日志(-o)与数据包保存(-w)功能则支持后续深入分析。

3.2 工程实现:灵活性与稳定性平衡

  • 命令行选项设计:通过getopt解析参数(如-D后台运行、-v verbose模式),满足不同场景需求(如长时间监控用守护进程模式,调试用verbose模式)。

  • 信号处理与统计:通过SIGINT(Ctrl+C)、SIGTERM信号捕获,在程序退出时输出统计信息(运行时间、接收/丢弃包数量,代码中sig_print_stats函数),方便评估捕获效果。

  • 兼容性考虑:支持与Kismet等工具协同(读取其dump文件),Kismet是主流的无线嗅探框架,专注于网络发现与帧捕获,而80211_sniff则专注于凭证解析,二者形成互补。

四、相关技术知识:从工具到生态

80211_sniff的实现依赖于无线网络与信息安全领域的成熟技术,理解这些知识能更深入把握其工作原理。

4.1 Kismet与无线帧文件格式

Kismet是一款开源无线嗅探器,能被动监听802.11网络,记录AP、客户端信息及原始帧数据(保存为wiretap格式,与Wireshark兼容)。80211_sniff的-r选项直接读取此类文件,解决了"实时捕获需现场操作"的限制,适合离线分析。

4.2 libpcap与跨平台捕获

libpcap是网络嗅探工具的"基础设施",支持Linux、Windows(WinPcap)、macOS等系统,统一了数据包捕获的接口。80211_sniff通过cap_opencap_setfilter等封装函数,简化了libpcap的使用,同时支持BPF过滤规则(如命令行后接表达式过滤特定流量)。

4.3 守护进程模式与后台监控

-D选项让80211_sniff以守护进程运行,适合无人值守的长期监控。此时程序会关闭标准输入输出,将日志重定向到文件(或/dev/null),通过dup2系统调用重定向文件描述符,确保进程在后台稳定运行。

五、总结与注意事项

80211_sniff作为一款轻量工具,聚焦于无线环境下的密码嗅探痛点,通过简化解析逻辑(放弃TCP流重组)、强化单包扫描,实现了对dsniff的有效补充。它的设计思路表明:优秀的安全工具需紧密结合场景特性——无线与有线的差异、丢包与完整流的权衡,都是功能设计的核心依据。

从技术发展看,随着WPA3普及与TLS加密的全面应用,传统密码嗅探的空间正逐渐缩小,但对老旧设备与特殊场景的审计需求仍将长期存在。80211_sniff这类工具的价值,也在于为安全人员提供分析手段,推动网络环境向更安全的方向发展。

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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐