// 安全研究 · 2026-09-25

逆向笔记:锐捷 802.1X 客户端多网卡检测机制与 VirtualBox 兼容补丁

目标:锐捷认证客户端 5.0.2014.108(RG_SU_For_Windows 6.61) 环境:Windows 10 / PowerShell 7 / objdump


TL;DR

锐捷的「多网卡检测」分三步:建可疑网卡名单 → 真实联网探测 → 弹窗并踢下线。

  • 建名单时豁免 PnpInstanceID 含 VMWARE 的网卡,所以 VMware 的 VMnet 一直无事;
  • 探测方式是把 socket bind 到每块候选网卡 IP,再真的去 connect www.baidu.com(最多 3 次);
  • 命中后调用 StopAuthentication(102),发 Logoff 报文并释放 IP,把你踢下线。

VirtualBox Host-Only 网卡(192.168.56.1)三条豁免都不沾、又通过了探测,于是中招。本文给出 3 处等长字节补丁(共 14 字节),让它彻底闭嘴。


1. 现象与取证

现象:启用 VirtualBox Host-Only 网卡后,认证几分钟内被踢,弹窗提示「服务器禁止设置多个IP来提供代理服务」,并点名 VirtualBox Host-Only Ethernet Adapter。

对照组:同机 VMware VMnet1 / VMnet8 带 IP 却一直平安。

安装目录:

C:\Program Files\Ruijie Networks\Ruijie Supplicant\
├─ 8021x.exe          ← 主程序(GUI + 认证协议 + 各种检测)
├─ suservice.exe      ← SYSTEM 权限后台服务
├─ Nic.dll / NetworkDetect.exe / W32N55.dll / snetcfg.exe
└─ logs\              ← 行为日志

8021x.exe:PE32、未加壳、镜像基址 0x400000,文件偏移 = 虚拟地址 − 0x400000。

日志(GBK 编码)里躺着完整证据链:

[2026-09-25 18:35:29] CContextControlThread::CheckMulNICDevice helpInfo 服务器禁止设置多个IP……
[2026-09-25 18:35:29] Enter CContextControlThread::StopAuthentication
[2026-09-25 18:35:29] nLogOffReson 102
[2026-09-25 18:35:29] SendLogOffPacket 释放ip

同流程在 18:35–20:07 重复 6 次,与被踢时间完全吻合。提示文案本身不在本地任何文件中,推测由服务器下发或运行时拼装。

字符串扫描(ASCII / UTF-16LE / GBK 三编码)显示:VirtualBox / VBox 命中 0 次,VMware 命中 2 次。这不是按名字匹配的黑名单——弹窗里的网卡名只是系统读到的描述原样打印,改网卡名骗不过检测。

关键锚点:

锚点字符串 文件偏移 VA
"www.baidu.com" 0x1B7944 0x5B7944
"GetProcxyDefenseStatus, bRet = 0x%x" 0x1B7FD0 0x5B7FD0
"nLogOffReson %d" 0x1B9B10 0x5B9B10
"CContextControlThread::CheckMulNICDevice" 0x1BA1FC 0x5BA1FC
跳过关键词表(miniport/video/pseudo/netpas/ras/wan) 0x1B88C4–0x1B88EC —

2. 静态分析:三条主干

反汇编:objdump -d -M intel --no-show-raw-insn 8021x.exe > dis.txt,字符串 VA = 文件偏移 + 0x400000,在 dis.txt 中 grep push 0x5b7944 找引用。

调用链总览:

[3 个调用点:网络状态事件处理 / 事件码 0x16 分发 / 状态同步]
    └─ CheckMulNICDevice (0x4432BF)
         ├─ 0x43D540  建「可疑网卡」名单
         │     ├─ GetAdaptersInfo 枚举(两段式:先取长度 0x6F,再取数据)
         │     ├─ 只要 Type==6(以太网) / 0x47(无线)
         │     ├─ 读注册表 Connection\PnpInstanceID → 含 "VMWARE" 跳过   ← VMware 免死金牌
         │     └─ 描述转小写 → Find(miniport/video/pseudo/netpas/ras/wan) → 跳过
         ├─ 0x434ED9  NicConnected:逐条探测
         │     └─ 0x434C60  bind 本网卡 IP → connect www.baidu.com(最多 3 次)
         ├─ GetProcxyDefenseStatus (0x4377CF):取服务器下发的「防代理」标志位
         └─ 名单非空 → 弹窗(资源 ID 0x127)+ 全局状态 0x475 + StopAuthentication(102)

建名单(0x43D540) 关键汇编:

43d5fd: mov  eax,[ebx+0x1a0]          ; IP_ADAPTER_INFO.Type
43d603: cmp  eax,0x6                  ; 以太网?
43d608: cmp  eax,0x47                 ; 无线?
43d622: lea  eax,[ebx+8]              ; AdapterName,即 {GUID}
43d628: call 0x43a086                 ; ← 读 ...\Connection\PnpInstanceID,返回是否含 "VMWARE"
43d62d: test al,al
43d62f: jne  0x43d7e5                 ; 命中 VMWARE → 跳过该网卡
43d64d: push 0x5b88e0                 ; "netpas"
43d655: call 0x40be85                 ; Find
43d664: push 0x5b88d0                 ; "video"
43d66c: call 0x40be85                 ; Find → 命中即跳过

本机注册表实测:

网卡 PnpInstanceID 结局
VMware VMnet1 / VMnet8 ROOT\VMWARE\0000 / 0001 ✅ 豁免
板载 Realtek PCI\VEN_10EC&… 认证网卡,走另一分支
VirtualBox Host-Only 不含 VMWARE ❌ 中招

探测(0x434C60) 伪代码:

WSAStartup(2, 2);
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, 1000ms);
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, 1000ms);
bind(s, {AF_INET, inet_addr(候选网卡的 IP)});      // ← 绑定到目标网卡
hostent* h = gethostbyname("www.baidu.com");
for (int i = 0; i < 3; i++) {
    SOCKET c = socket(...);
    if (connect(c, h->addr, 80) 判定通过) { *out = 1; break; }
}

即按「这块网卡是否真能连通外网」认定你有没有第二条上网出口。这种探测相当糙:bind 只改源地址,路由仍按目的地址选默认路由;再叠上 Windows 弱主机模型,虚拟网卡通断判定本就时灵时不灵。这也是它必须靠白名单兜底的原因——而白名单里唯独没有 VirtualBox。

判定与处罚(CheckMulNICDevice 0x4432BF):

4434d7: mov  ebx,[ebp-0x28]          ; 拼好的「多余网卡」名单(CString)
4434da: cmp  DWORD PTR [ebx-0xc],0   ; 名单为空?
4434de: je   0x443575                ;   ├─ 空 → 另一分支
...
443573: jmp  0x44357b                ;   └─ 非空 → 落入「处罚块」
443575: cmp  BYTE PTR [ebp-0xd],0    ; 辅助标志路径
443579: je   0x4435ee                ;   └─ 为 0 → 直接收尾
44357b: mov  edi,[ebp-0x18]          ; ┐
44357e: push 0x1                     ; │
443580: push 0x1                     ; │ 处罚块
443582: push 0x66                    ; │ 下线原因 102
443584: mov  ecx,edi                 ; │
443586: call 0x44180f                ; ┘ StopAuthentication(102) → Logoff + 释放 IP

自校验排查:对「篡改 / 校验 / 完整性 / MD5 / Checksum / 重新安装」全目录三编码扫描,命中的只有 EAP-MD5、下载组件完整性校验、私有协议签名,均与 8021x.exe 自身 SHA1 不匹配。结论:无自校验,可放心改。


3. 补丁设计

两条思路:

  1. 拆引信——把「处罚块」两个入口全部改成无条件跳过:探测爱跑跑,弹窗和 StopAuthentication(102) 永不执行;
  2. 隐身——把跳过关键词表里的 video 改成 virtu,让 VirtualBox … 描述在建名单阶段直接命中「跳过」。(匹配前描述转小写,virtualbox host-only ethernet adapter 含 virtu。)

全部为等长字节替换:不增减字节、不移动偏移、不动区段/导入表/文件头,无需修复 PE。

文件偏移 原始字节 补丁后 说明
0x434DE 0F 84 91 00 00 00(je) E9 92 00 00 00 90(jmp+nop) 名单非空不再进处罚块
0x43579 74 73(je) EB 73(jmp) 辅助标志路径不再进处罚块
0x1B88D0 "video" "virtu" VirtualBox 网卡建名单即跳过

副作用:处罚块成为不可达代码;认证、DHCP、无线、VMware 豁免等逻辑均不受影响;可随时用备份还原。

补丁脚本 patch-ruijie.ps1

特性:管理员/进程占用检查、自动备份(8021x.exe.orig,不覆盖)、改前逐字节核验(版本不符直接中止)、改后复检、可重复运行。

# 锐捷 8021x 客户端补丁
# 作用: 关闭"多网卡检测"的弹窗+踢下线动作, 并让 VirtualBox 网卡在筛查中被跳过
# 适用: 锐捷认证客户端 5.0.2014.108 (8021x.exe, 2337088 字节)
# 可重复运行: 已打过会提示"已是补丁状态"; 客户端升级/重装后重跑一次即可。
# 运行前请先退出锐捷客户端; 需要管理员权限。

$exe = 'C:\Program Files\Ruijie Networks\Ruijie Supplicant\8021x.exe'
$bak = $exe + '.orig'

function Main {
    $admin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $admin) {
        Write-Host '[X] 需要管理员权限。请关闭本窗口, 右键 patch-ruijie.cmd 选择"以管理员身份运行"。' -ForegroundColor Red
        return 1
    }
    if (Get-Process -Name '8021x' -ErrorAction SilentlyContinue) {
        Write-Host '[X] 锐捷客户端正在运行。请先在托盘右键退出它, 再运行本补丁。' -ForegroundColor Red
        return 1
    }
    if (-not (Test-Path -LiteralPath $exe)) {
        Write-Host ('[X] 找不到: ' + $exe) -ForegroundColor Red
        return 1
    }

    if (-not (Test-Path -LiteralPath $bak)) {
        Copy-Item -LiteralPath $exe -Destination $bak
        Write-Host ('[OK] 已备份原文件 -> ' + $bak)
    } else {
        Write-Host ('[i] 备份已存在(未覆盖): ' + $bak)
    }

    $bytes = [IO.File]::ReadAllBytes($exe)
    if ($bytes.Length -ne 2337088) {
        Write-Host ('[X] 文件大小 ' + $bytes.Length + ' 与预期 2337088 不符, 版本可能已更新, 已中止。') -ForegroundColor Red
        return 2
    }

    $patches = @(
        @{ Off = 0x434DE;  Orig = [byte[]](0x0F,0x84,0x91,0x00,0x00,0x00); New = [byte[]](0xE9,0x92,0x00,0x00,0x00,0x90); Name = '多网卡名单路径: 不再弹提示/踢下线' },
        @{ Off = 0x43579;  Orig = [byte[]](0x74,0x73);                     New = [byte[]](0xEB,0x73);                     Name = '多网卡兜底路径: 不再弹提示/踢下线' },
        @{ Off = 0x1B88D0; Orig = [byte[]](0x76,0x69,0x64,0x65,0x6F,0x00); New = [byte[]](0x76,0x69,0x72,0x74,0x75,0x00); Name = '网卡筛查关键词 video -> virtu (VirtualBox 网卡被直接跳过)' }
    )

    $apply = @()
    $done = 0
    foreach ($p in $patches) {
        $isOrig = $true
        $isNew = $true
        for ($i = 0; $i -lt $p.Orig.Count; $i++) {
            if ($bytes[$p.Off + $i] -ne $p.Orig[$i]) { $isOrig = $false }
            if ($bytes[$p.Off + $i] -ne $p.New[$i])  { $isNew  = $false }
        }
        if ($isNew) { $done++; continue }
        if (-not $isOrig) {
            $expect = ($p.Orig | ForEach-Object { '{0:X2}' -f $_ }) -join ' '
            Write-Host ('[X] 偏移 0x{0:X} 的字节与预期不符 (应为 {1}), 文件可能已被改动或是其它版本, 已中止。' -f $p.Off, $expect) -ForegroundColor Red
            return 2
        }
        $apply += $p
    }

    if ($apply.Count -eq 0) {
        Write-Host ''
        Write-Host '[i] 3 处补丁均已是生效状态, 无需重复打补丁。'
        Write-Host '[完成] 现在可以直接打开锐捷客户端登录。' -ForegroundColor Green
        Write-Host '  [还原] 如需还原: 运行 restore-ruijie.cmd。'
        return 0
    }

    foreach ($p in $apply) {
        for ($i = 0; $i -lt $p.New.Count; $i++) { $bytes[$p.Off + $i] = $p.New[$i] }
        Write-Host ('[OK] {0}  (0x{1:X})' -f $p.Name, $p.Off) -ForegroundColor Green
    }
    if ($done -gt 0) { Write-Host ('[i] 另有 ' + $done + ' 处此前已生效。') }

    [IO.File]::WriteAllBytes($exe, $bytes)

    $v = [IO.File]::ReadAllBytes($exe)
    $ok = $true
    foreach ($p in $patches) {
        for ($i = 0; $i -lt $p.New.Count; $i++) {
            if ($v[$p.Off + $i] -ne $p.New[$i]) { $ok = $false }
        }
    }
    if (-not $ok) {
        Write-Host '[X] 写入后校验失败! 请运行 restore-ruijie.cmd 还原。' -ForegroundColor Red
        return 3
    }

    Write-Host ''
    Write-Host '[完成] 补丁已写入并校验通过。现在可以重新打开锐捷客户端登录了。' -ForegroundColor Green
    Write-Host '  [提示] 客户端升级/重装后补丁会失效, 重新运行本脚本即可。'
    Write-Host '  [还原] 运行 restore-ruijie.cmd, 或把 8021x.exe.orig 复制回 8021x.exe。'
    return 0
}

$r = Main
Write-Host ''
Read-Host '按回车键关闭'
exit $r

启动器 patch-ruijie.cmd(双击后弹 UAC 提权):

@echo off
cd /d "%~dp0"
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process powershell.exe -Verb RunAs -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','%~dp0patch-ruijie.ps1')"

还原脚本核心两行:

Copy-Item -LiteralPath ($exe + '.orig') -Destination $exe -Force   # 前提: 先退出客户端, 管理员运行

手动核对补丁是否生效:

$exe='C:\Program Files\Ruijie Networks\Ruijie Supplicant\8021x.exe'
$b=[IO.File]::ReadAllBytes($exe)
'{0:X2} {1:X2} {2:X2} {3:X2} {4:X2} {5:X2}' -f $b[0x434DE..0x434E3]
# 期望输出: E9 92 00 00 00 90

4. 使用、验证与局限

使用步骤:

  1. 托盘右键退出锐捷客户端(补丁需要独占文件;会短暂断网);
  2. 右键 patch-ruijie.cmd → 以管理员身份运行;
  3. 重新打开客户端登录。

验证点:

  • 挂上 VirtualBox Host-Only 网卡不再被踢、不再弹「多个IP」提示;
  • logs\wireless.log 中该网卡会被标为 strDescription: virtualbox host-only ethernet adapter - skip(隐身生效);
  • 认证、DHCP、无线、VMware 均照常。

还原:运行 restore-ruijie.cmd,或把 8021x.exe.orig 覆盖回去。

局限与风险:

  • 补丁只对 2337088 字节的这一版有效;客户端升级/重装后偏移会变,脚本会检测到字节不符并中止——别硬打,按第 2 节方法重新定位即可(搜字符串 → 算 VA → grep push → 读函数,套路不变)。
  • 这种探测实现(bind + connect)对虚拟网卡本就不可靠;补丁等效于「拆掉处罚动作」,任何探测结果都不会再触发下线。
  • 本文仅供逆向学习与兼容性研究(如让本机实验环境的虚拟机网卡不被误判)。校园网大都有明确的网络使用规定,是否使用、如何使用,风险自负。

原文 https://blog.csdn.net/2301_79518550/article/details/166646481