Skip to content
FrameFrame home
PlatformBreach CheckFrame agentExtensionPrivacyBusinessPricing
Sign inSample report
FrameFrame home

Your digital life. Back in your hands.

100% Canadian-owned · Support: support@frameprivacy.com

Product

  • Platform
  • Sample report
  • Breach Check
  • Frame agent
  • Extension
  • Mobile app
  • Pricing
  • Frame Business
  • Changelog

Resources

  • Blog
  • Help centre
  • Free leak test
  • Status
  • Data-broker catalog
  • RSS feed

Company

  • About
  • Our privacy promise
  • Contact
  • Security
  • Press and brand

Legal

  • Legal hub
  • Privacy Policy
  • Terms
  • Cookies
  • Accessibility

© 2026 Frame. All rights reserved.

en Englishfr Français
Your Privacy ChoicesSystem status
Frame

All posts

Malware · October 16, 2026 · 17 min read

Feeds called it Mirai. The handshake starts with ANCT.

KSI Digital watched a Linux bot resolve its server over DNS-over-TLS, then send ANCT. The same builds still trip a Mirai rule. The older botnet on that domain did not.

BBrody · Frame
A pale field of dots, one steel line that passes through a ring and breaks before a dark box.

On October 4, 2026, a machine logged in to a Telnet honeypot run by KSI Digital and pulled down a shell script called persist.sh. The script’s first comment names the payload: ancient. Over the next four hours the same machine fetched three versions of that script and two builds of a hidden Linux program, .ancient. The next morning, in a lab with every other destination blocked, the program looked up its command server over DNS-over-TLS and opened a session whose first four bytes are ANCT.

MalwareBazaar had already filed builds of that program as Mirai. Elastic’s Linux_Trojan_Mirai signatures fire on the x86-64 ones. The name the program looks up, zyrec2.duckdns[.]org, was already on ThreatFox as a Mirai command server. Both of those facts are real. Neither one is the protocol.

The protocol KSI recorded is four ASCII bytes, then 32 high-entropy bytes from the bot, then 36 from the server, then a stream the bot kept uploading for ten minutes. The server sent nothing else. About 1.79 MB went up. Nobody in the public write-up can say what those bytes were. Frame did not run the samples and did not contact the server. What follows is the published record, including the places it does not match the Mirai story the feeds are telling.

What “Mirai” is doing in the label

Mirai is a family of Linux malware for routers, cameras, and recorders. A bot scans, logs in with a short list of default passwords, downloads a build for the CPU it found, and waits for an attack command. The shape is so common that a new ELF which arrives by Telnet, ships an ARM build and an x86-64 build, and hides itself with a leading dot gets the Mirai tag before anyone reads the command channel.

KSI’s detection note is more precise than “the feed was wrong.” They obtained nine bot builds, four x86-64 and five ARMv7, dated October 3 through October 5. MalwareBazaar labels all nine Mirai. Elastic’s Linux_Trojan_Mirai rule matches the x86-64 builds. KSI’s note is that the hit is consistent with a Mirai-derived core under a custom command layer. The body can be Mirai’s. The mouth is not.

That split is why a strings search on the compiled bot is a bad way to settle the argument. KSI says the program builds its command-server name, its port, and the ANCT tag at runtime. A rule that looks for zyrec2.duckdns.org inside the bot will miss it. Their own bot rule does not look for the domain. It looks for persistence module names that are plain text in every build they could get: home_persist, cron_deb, android_rc, rc_local, a sysd init script, and a sys.service user unit, plus the toybox and busybox download chain. Those strings are a Mirai-style installer menu. They are not a Mirai handshake.

The dropper is a separate file, and it does carry plain text. The script opens with a comment that it is an ancient Telnet persistence payload and that it must stay pure ASCII, because old BusyBox shells mishandle multi-byte UTF-8. It maps uname -m onto thirteen builds: arm4 through arm8, mips, mpsl, x86, x86_64, m68k, ppc, and sh4. mpsl is the little-endian MIPS name Mirai’s build scripts have used for years. The architecture list is one more reason a classifier leans Mirai. The list is the loader’s menu. It is not the command protocol.

The domain was already someone else’s Mirai

zyrec2.duckdns[.]org is not a name KSI discovered. In April 2026 Tao Goldi published ZyreC2, a Mirai-derived DDoS bot whose unstripped binaries still contain the build path /root/Zyre C2/. Those samples resolve the same DuckDNS name, and they do it in a way Ancient does not.

ZyreC2, April 2026Ancient, October 2026
Who documented itTao Goldi, from unstripped symbolsKSI Digital, from a honeypot and one live session
Domainzyrec2.duckdns[.]org, stored as a stringthe same name, built at runtime
Lookupa raw DNS query to 8.8.8.8 on port 53, then a hardcoded IP if that failsDNS-over-TLS to 1.1.1.1 on port 853, with plain DNS as a fallback
Command port950635342, on the host KSI watched
First bytes00 00 00 02 08, then the tag zyre.dbgANCT, then 32 bytes, then a 36-byte reply
After thatplaintext commandsencrypted, contents unpublished
Binaryabout 256 KB to 300 KB, symbols left in437,232 bytes for each x86-64 build KSI lists, stripped
DNS-over-TLSnot in the published samples; variant C links no TLS libraryobserved on the x86-64 build they executed

Goldi’s variant C, first seen April 19, still uses port 9506 and still speaks plaintext. He checked: nm returns no SSL, TLS, libssl, or libcrypto symbols. Two strings that look like TLS, tls_handshake and tls_done, sit in the debug info. They are enum names from the source. They are not a TLS session on the wire.

So the question KSI left open, whether Ancient is related to the older use of that domain, has a partial public answer. The name was a Mirai command name months earlier, for a botnet that looks up DNS in the clear and talks in the clear. The October program that looks the name up over TLS, and then sends ANCT, is not that program. Sharing a DuckDNS hostname is cheap. DuckDNS is a dynamic-DNS provider; the address behind the name can change without the binaries changing. Goldi watched it change once already, from a Hetzner address in Germany to 176.65.139[.]253, which he attributed to AS214472. KSI, writing in October, says ThreatFox has listed the domain as a Mirai command server since June 2026, under indicator 1822702. Frame could not reload that ThreatFox page. The June date is KSI’s, and it sits between Goldi’s April samples and the October capture.

Nothing in either write-up shows the April bot speaking ANCT, or the October bot speaking ZyreC2’s five-byte registration. Treating the domain as “the Ancient C2” without a port and a handshake will also catch, or miss, a different family.

Three droppers in four hours, and a port written backwards

The infected device was 94.154.43[.]138. At 15:37 UTC on October 4 it logged in over Telnet. The command it ran is the usual IoT chain: try /tmp, then /var/run, then /mnt, then /root, then /, and pull persist.sh from 89.163.157[.]131 on port 8080. KSI logged 34 commands and downloads from that address over the next four hours.

The script tries wget, BusyBox wget, curl, BusyBox curl, and toybox wget, in that order. It keeps the download only if the file is larger than 100 KB, saves it as a hidden .ancient, starts it detached, and prints one of four words back to the loader: ANCIENT_STARTED, ANCIENT_NOCONN, ANCIENT_SKIP, or ANCIENT_FAIL.

VersionSizeSHA-256What changed
v1, 15:37 UTC2,802 bytes5e6c83022a7415047f7e9d9cb89e43d6de7522801b353ce10eda85c018be76c8first writable directory; cron, rc.local, and init.d
v2, that afternoon3,380 bytes659742f14304f2ec1cf1ec34833b93002cd360835fff3073d19ea2edf23d6bearefuses a second copy, so the server is not closing duplicate sessions from one address
v3, 19:09 UTC5,952 bytesac62a1dfc283429341b99196d974da0a18ffe4f4e30ac28eb4e9583aed4b592fpicks a filesystem that survives reboot; adds /etc/crontab, root’s cron spool, and /etc/profile.d/.ancient.sh; fixes the port check
a fourth, on MalwareBazaar at 23:14 UTC on October 55,765 bytes203727d32428db693546eb29ab6a12b52a455b7d2a8c58ea8c57c70abb2f1aabhas v3’s mount scoring, and still has v1’s port bug

The port bug is the part worth reading slowly. After the bot starts, the script looks in /proc/net/tcp for a connection on port 35342. That port in hexadecimal is 8A0E.

8 × 4096 + 10 × 256 + 14 = 35342.

Linux prints that table with the port in host byte order. On the machines this bot cares about, the text to find is :8A0E. Versions 1 and 2 search for :0E8A. That is the same two bytes, swapped, which is what you get if you treat the port as a big-endian word and then go looking for those bytes as the printed word. The search never hits. Those versions always report ANCIENT_NOCONN, which means “I do not see the command session,” even when the session is there. Version 3 searches for :8A0E and says so in a comment. The authors were reading the failures.

The fourth script, uploaded by someone else, is the awkward middle. It already scores /proc/mounts the way v3 does, so it prefers a directory that is still there after reboot, but it still searches for :0E8A. It also reports an already-running copy as ANCIENT_STARTED, where v3 says ANCIENT_SKIP. KSI’s dropper rule matches it anyway. Either the server was still handing out an older build, or someone rolled back. The comment in v3 points at a file called TELNET_PERSIST_PAYLOAD.sh for the mount-scoring idea. That name does not appear in the Ancient protocol. It reads like a shared loader note.

Persistence on the host, once a version gets that far, is a cron line every five minutes, rc.local, /etc/init.d/.ancient, and, from v3, /etc/profile.d/.ancient.sh. The cron indicator KSI published is */5 * * * * followed by the hidden binary. The binary’s own strings add Android rc and a systemd user unit. A device that has no systemd will not run that unit. The string is still in the file.

DNS-over-TLS is not the same trick as the April bot

DNS-over-TLS is ordinary DNS, carried inside TLS, on TCP port 853. The standard is RFC 7858. A log that only records UDP and TCP port 53 never sees the question or the answer. What it sees is a TLS session to a public resolver. In this case the resolver was Cloudflare’s 1.1.1.1.

KSI’s first runs were under a tracer, for 10 to 25 minutes. The bot opened no sockets until the tracer detached. The next run was untraced, and the only allowed destination was that resolver on port 853. The bot resolved zyrec2.duckdns[.]org immediately: 32 DNS-over-TLS connections, plus 6 plain DNS queries for the same name, then 33 attempts to reach 89.163.157[.]131 on port 35342. The lab router blocked those 33. The name came back as that address. The payload server and the command server were the same machine, AS24961, myLoc / WIIT AG, in Germany. Downloads on 8080, commands on 35342.

Two things follow from the 32-and-6 count, and KSI does not pick between them. The bot will use encrypted DNS when port 853 answers. It will also ask in the clear. A network that blocks 853, and watches port 53, still gets the name. A network that allows 853 to 1.1.1.1 and only alerts on port 53 does not. Blocking one of those paths is not coverage of the lookup.

The April ZyreC2 samples do not share this behavior. They query 8.8.8.8 on port 53, and if that fails they use an IP compiled into the binary. Ancient’s published note does not list a hardcoded command address. The fallback is a second DNS query, not a baked-in IP. Frame found no second public sample, outside this family, whose write-up shows this DNS-over-TLS pattern tied to ANCT. The other eight builds match KSI’s bot rule, which is evidence they are the same program. It is not a second packet capture of the TLS lookup. The lookup was observed on the x86-64 build they executed, 6c44cbf5eb5e4f5231964a2fb24ef466b17e967f0d26f57c8285ade7424970db.

The handshake, and the megabyte nobody can read

With port 35342 allowed, and nothing else, they let one session run from 02:01 to 02:11 UTC on October 5.

The bot sent 41 4E 43 54. That is ANCT. KSI reads it as a shortening of “ancient,” which matches the file name, the status words, and the comment at the top of the script. Then the bot sent 32 bytes that do not compress into text. Then the server sent 36. Then the bot uploaded about 1.79 MB, in 2,785 packets, most of them 734-byte segments. The server’s only reply was those 36 bytes. The session stayed open for the whole ten minutes. That is the evidence the server was alive. It is not evidence of a command.

Thirty-two bytes is the size of an X25519 public key, and 36 is 32 plus a four-byte tag. KSI says the 32 bytes are consistent with an ephemeral key exchange. They also list the cipher as an open question. Consistency is not an identification. Frame is not going to name the algorithm past what they wrote.

The upload is easier to bound than to explain. Ten minutes is 600 seconds. 2,785 packets in that window is about 4.6 packets a second. 1.79 MB over 600 seconds, taking the figure as the decimal they wrote, is about 3 kilobytes a second. That is a drip. It is not the bot launching a flood through this socket. Floods in this kind of malware are aimed at a victim, from the bot, on the bot’s own sockets. Here the direction is bot to command server, the server is not issuing work, and the size is a couple of megabytes once.

What fits inside an unexplained upload of that size, and what does not:

  • It is not the server pushing a module. The server sent 36 bytes and then stopped.
  • It is not a copy of the bot. The x86-64 builds are 437,232 bytes, about a quarter of 1.79 MB, and the bot already had itself.
  • It could be a report: addresses scanned, files seen, a repeated encrypted record. 734-byte segments are smaller than a normal Ethernet payload, so they look like an application size rather than the network’s maximum. KSI does not claim a record format.
  • It could be padding that keeps the session from looking idle. That is a guess. The write-up does not make it.

Their Suricata rule for the handshake is not locked to port 35342. The first four payload bytes are the signal, on whatever port the next build uses. The IP and the port are dated. They said so, and they notified the hosting provider the same day.

It stays quiet while you watch

The traced runs are the other half of why a sandbox can file this as a generic Mirai and never see ANCT.

For as long as a tracer was attached, there was no DNS and no command session. When the tracer came off, the traffic started. KSI does not publish the check. The usual Linux version of this trick is to look at TracerPid in /proc/self/status, or to call ptrace on yourself, which fails if someone already has you. Either one produces exactly the behavior they saw: idle under strace, busy the moment strace exits. Naming those two techniques is the class of check. It is not a claim that this binary contains a particular call. The strings that would prove it may be built at runtime, the same way the domain is.

A sandbox that traces by default, and that scores a sample on the network traffic it produced while traced, gets a quiet ELF that arrived by Telnet. The Mirai label is then the delivery, the architecture menu, and Elastic’s rule on the bytes. The command channel never happened.

Same loader, three payloads, and a /24 with a past

The Telnet routine in front of the download is short. It opens two connections and drops one, logs in as root with an empty password, then runs a BusyBox probe, reads /proc, and tries ./, about a second apart. KSI looked for that sequence across three days and 163 Telnet logins. Three addresses used it.

AddressNetwork, as KSI gives itWhat it actually delivered
94.154.43[.]138AS219502Ancient, from 89.163.157[.]131, on October 4. The probe’s first line was ancient_telnet_ok.
94.154.43[.]196AS219502a Mirai kit, kla.sh and nine builds, from 176.65.139[.]196, on October 5 at 12:58 UTC. The probe’s first line was SHELL_TEST.
77.239.124[.]121AS198364the probe only, three visits, no payload

AS219502 is STORMCLOUD-AS, Storm Industries LLC. KSI says RIPE registered it on June 9, 2026, and that it started announcing 94.154.43.0/24 and 176.65.139.0/24 in June and July. Ancient’s own server is not in that network. It is the German host above.

176.65.139[.]196 is both the Mirai kit’s download server and its command server, on port 18129, which KSI says has been on ThreatFox since October 1 (indicator 1943514). An update on October 7 adds a third kit aimed at the same network. A loader called telnet2.sh, from 93.127.112[.]90 outside AS219502, fetches zerobot builds from 176.65.139[.]157. The ARMv7 build, which KSI calls a Condi-style bot, connects to 94.154.43[.]30 on port 695, already on ThreatFox as indicator 1952211, and scans ports 80, 8080, 37215, and 52869. Five more addresses in 94.154.43.0/24 logged in between October 3 and October 5.

This is the sense in which Ancient “masquerades” as Mirai, and the sense in which it does not. The loader will say ancient_telnet_ok on Monday and SHELL_TEST on Tuesday and then install a Mirai kit. An analyst who clusters by the Telnet prologue will merge three families. An analyst who clusters by the command channel will not. KSI’s own conclusion is the one the data supports: one operator running several kits, or a loader service with several customers. Their honeypot cannot tell those apart.

The April overlap is weaker, and it should stay weak. Goldi’s ZyreC2 variant C has 176.65.139[.]253 compiled in, and he attributed that address to AS214472 in the Netherlands. That is a different last octet, a different autonomous system in his April note, and a protocol that is not ANCT. KSI’s October note puts the /24 under AS219502, an AS that did not exist in the RIPE record until June. A prefix can move. A shared /24 plus a shared DuckDNS name is a lead. It is not an identification of the October ANCT server with the April Minecraft-flood bot.

The nine builds, so the label can be checked

These are the bot hashes in KSI’s indicator list. All nine are what MalwareBazaar calls Mirai. The sizes are theirs.

First seen (UTC)ArchBytesSHA-256
2026-10-03 17:42ARMv7449,2764c49dbbce842ee8489eb38080e2eb258cc15aad68aab77aa1e7a38e691ab948a
2026-10-04 00:19ARMv7449,4842f4cbba58e684daa23273aae6f0454f78b4f60502826a185ac0ddfe49c6a686a
2026-10-04 06:53ARMv7466,45236d7214b35c74d8784cf0a74e337e7902864d38e822f7c9979133d6123c6bd00
2026-10-04 12:18ARMv7466,964ad7af34fa799ab9a16df4695068965e40195d2f7b7da176605cb35819647c9f1
2026-10-04 13:37x86-64437,232c9d1bf7bfe1818e071b6b71d10e0a4f2f0a4fcdfcb510acd83fc1f098166c3ee
2026-10-04 15:37x86-64437,2326c44cbf5eb5e4f5231964a2fb24ef466b17e967f0d26f57c8285ade7424970db
2026-10-04 17:06x86-64437,232c86a0cee1adc167f3c3c1677b3f1e294af418cd3cc57c2aead717573b7fce6c8
2026-10-04 19:09x86-64437,232748f50d41a7232d012e1eefdbe9207cc448c2710335989badf458a4720dd717b
2026-10-05 00:21ARMv7467,540507611a45de2fa9f20c1e26e4274b655faf7f47cb2688a1782da25604a2e34c6

The family was on MalwareBazaar a day before the honeypot session. The four x86-64 files are the same length and not the same hash, so they are rebuilds, not one file uploaded four times. The ARMv7 files grow across October 4. KSI’s rule matched all nine, and it did not match the other Linux families they tried it against: Mirai, Gafgyt, Mozi, XorDDoS, and the rest of a set of roughly 260 recent samples, plus their own other honeypot captures, plus the system files of an Ubuntu 24.04 machine.

That last test is the right size of claim. A rule with no false positives on 260 neighboring samples is a useful start. It is not a census of the internet. Public packet evidence of ANCT, as of their October 5 write-up and the pages that repeat it, is one ten-minute session. Malpedia’s entry for elf.ancient points back at the same GitHub document.

If a router on your network starts resolving this way

  1. Turn Telnet off on anything that faces the internet. The loader KSI watched logged in as root with an empty password. A device that still accepts that, or that still has the vendor’s password, is the whole story of how this spreads. Replace the password, update the firmware from the vendor, and remove management from the open internet. A camera you cannot update is a camera to replace or unplug.
  2. Do not clear a host because a product called the file Mirai, and do not clear it because a Mirai rule did not fire. On the x86-64 builds, Elastic’s Mirai rule fires and the command channel is still ANCT. On a traced sandbox, the channel may never open. The check that distinguishes this session is the first four payload bytes, 41 4E 43 54, on the way out, on any port. Port 35342 is the port they saw. KSI’s rule does not depend on it staying.
  3. Watch DNS-over-TLS from the IoT network, which is TCP 853 to a public resolver, and also watch ordinary DNS for zyrec2.duckdns[.]org. The bot did both. A sinkhole that only rewrites port 53 leaves the TLS path. A block that only drops 853 leaves the clear path. The domain alone is not Ancient: in April it was ZyreC2, on port 9506, in the clear. Pair the name with the port and with ANCT before you treat a hit as this family.
  4. On a device you can inspect, look for a hidden .ancient, for /etc/init.d/.ancient, for /etc/profile.d/.ancient.sh, and for a cron entry every five minutes that runs that file. The binary may also contain the module names above even when the shell dropper is gone. Those names are not proof of a live session. A live session still looks like ANCT on the wire.
  5. The addresses in the October 5 note are leads with a date on them: 89.163.157[.]131 for the payload and the command port they confirmed, and the loader addresses above for the Telnet prologue. KSI reported the command server to ThreatFox, the downloads to URLhaus, and the address to Spamhaus, and they told the hoster. A block list from October 5 is not the handshake. The handshake is what still works if the host moves.

Sources

  • KSI Digital, Christophe Hubert, “Ancient”: a Linux IoT botnet with a custom ANCT C2 protocol, October 5, 2026, and the indicator list and detection notes in the same repository.
  • Malpedia, elf.ancient, added October 6, 2026, citing that write-up.
  • Tao Goldi, ZyreC2: The Game-Obsessed Mirai Fork That Left Its Homework Out, April 14, 2026, with the April 20 variant C update. This is the earlier botnet on zyrec2.duckdns[.]org. It is not the ANCT session.
  • YARAhub, as linked by KSI: Linux_Ancient_Bot and Ancient_Telnet_Dropper.
  • ThreatFox indicators 1822702, 1943514, and 1952211, as cited in the KSI write-up. Frame did not re-fetch those pages.
  • Malware
  • Research

New posts, by email.

About once a month. Plain-language privacy notes, nothing else. I never share your address.

Sent by Frame · privacy@frameprivacy.com · Privacy Policy

See what’s exposed about you.

Frame is in private beta. See what it finds, and how it helps you take it down, in a sample report built from made-up data.

See a sample exposure report
FrameFrame home
PlatformBreach CheckFrame agentExtensionPrivacyBusinessPricing
Sign inSample report

See what’s exposed about you.

Frame is in private beta. See what it finds, and how it helps you take it down, in a sample report built from made-up data.

See a sample exposure report