A Random Filename, Six Years of Infection

Four files in my honeypot’s sample directory were exactly 4,766 bytes each. Same size, four different dates across June and July, four different SHA-256 hashes. The file command called all of them data. Not text, not ELF, nothing it could name.

They contain the same payload, byte for byte. Eight bytes separate them, and those eight bytes have nothing to do with the malware.

My first assumption was that this broke hash-based detection. A random value inside the captured artifact would produce a worthless indicator every time, or so I thought. VirusTotal disagreed. It knows all four hashes, and it has known one of them since November 2019.

Those eight bytes are not noise. They fingerprint individual infected hosts across years and across unrelated sensors.

The sensor

A t3.nano running Ubuntu 24.04 on EC2, deployed with Terraform, running the SANS Internet Storm Center DShield stack with cowrie emulating an SSH shell. I left it collecting for three months without looking at it.

Five files stood out by size alone.

1
2
3
4
5
4766  2026-06-05  783565adc1ddbf9dab030a11f90d0c5897949504140a4c52458a3b7236f10486
4766  2026-06-17  e63969f07eb117998329f37cb3543d83c76c1260d6122120ec7b7d256676b022
4766  2026-06-30  d946dbc57684f968666466909956619625332e869731f95d11152fd48d104fad
4766  2026-07-09  b4c8f6e4e5ca7f71f5b94470d34880aa66d25bf88bbf405a0365ba3ef15db829
4745  2026-06-07  6d1fe6ab3cd04ca5d1ab790339ee2b6577553bc042af3b7587ece0c195267c9b

Five deliveries in five weeks. Four at an identical size the file command refused to identify, and one 21 bytes shorter that it correctly recognized as a bash script.

Reading the first 32 bytes

Encrypted content was the obvious guess. A hex dump ruled it out immediately.

1
2
00000000: 4330 3735 3520 3437 3435 2054 5a49 6671  C0755 4745 TZIfq
00000010: 584c 390a 2321 2f62 696e 2f62 6173 680a  XL9.#!/bin/bash.

That is the SCP protocol, not encryption. When a file is pushed with scp, the sender transmits a control line ahead of the data:

1
C<mode> <size> <filename>\n

Mode 0755, size 4745, filename TZIfqXL9. The data stream terminates with a single null byte. The arithmetic resolves cleanly: 20 bytes of header, 4,745 bytes of payload, one null byte, 4,766 total. Cowrie stores the raw wire stream with the header intact and names the file on disk after the SHA-256 of the whole thing.

Recovering the usable script takes one command.

1
tail -c +21 sample.bin | head -c 4745 > payload.sh

The fifth sample, at 4,745 bytes, arrived through a different mechanism and was stored without a wrapper. A header-stripped extraction from any of the other four matches it exactly, across its full length. One payload, five deliveries.

Where the filename comes from

Comparing two wrapped samples byte by byte isolates the difference precisely.

1
cmp -l 783565adc1dd... b4c8f6e4e5ca...
1
2
3
4
5
6
7
8
  12 124 163
  13 132  62
  14 111 104
  15 146 151
  16 161  61
  17 130 127
  18 114  70
  19  71  63

Eight differing bytes at offsets 12 through 19, which fall inside the filename field of the SCP header. TZIfqXL9 in one sample, a different eight characters in the next.

The payload is a self-propagating SSH worm. Its propagation loop is short enough to read in full:

1
2
3
4
5
6
7
8
9
NAME=`mktemp -u 'XXXXXXXX'`          # generated once, before the loop

while [ true ]; do
        FILE=`mktemp`
        zmap -p 22 -o $FILE -n 100000
        for IP in `cat $FILE`; do
                sshpass -p<password> scp ... $MYSELF pi@$IP:/tmp/$NAME
        done
done

Two things are fixed here rather than random per delivery. $NAME is computed once, outside the infinite loop. More importantly, so is $MYSELF, and tracing where it comes from is what makes the header meaningful.

The worm reaches a victim over scp, then runs over SSH as the pi user. That user is not root, so the first branch of the script executes:

1
2
3
4
5
if [ "$EUID" -ne 0 ]; then
        NEWMYSELF=`mktemp -u 'XXXXXXXX'`
        sudo cp $MYSELF /opt/$NEWMYSELF
        sudo sh -c "echo /opt/$NEWMYSELF >> /etc/rc.local"
        sudo reboot

$NEWMYSELF is drawn on the victim itself. After the reboot, rc.local launches /opt/$NEWMYSELF as root, $EUID is zero, and the propagation branch runs. From that point on, for the lifetime of that machine’s infection, $MYSELF is /opt/$NEWMYSELF.

The last link is a property of the SCP protocol rather than of the worm. In a transfer, the sender announces the basename of the source file, not the destination. The receiver writes to the destination path because the target is a file, but the name on the wire is the source’s. One command demonstrates it:

1
2
3
$ echo test > /tmp/AAAAAAAA
$ scp -v /tmp/AAAAAAAA user@host:/tmp/BBBBBBBB 2>&1 | grep 'Sending file modes'
debug1: Sending file modes: C0644 5 AAAAAAAA

The destination is BBBBBBBB, the announced name is AAAAAAAA.

So the filename in the captured header is $NEWMYSELF: a value drawn once, on the attacking machine, at the moment that machine was compromised, and invariant for every attack it launches afterwards.

The VirusTotal timeline

If that reading holds, each wrapped hash should have a sighting history that predates my capture, because every other honeypot the same host ever reached recorded identical bytes.

HashCaptured hereFirst seenLast seenSubmissionsDetections
d946dbc576…2026-06-302019-11-262026-06-191440/74
783565adc1…2026-06-052021-07-012026-05-022039/75
b4c8f6e4e5…2026-07-092021-10-012025-10-08639/76
e63969f07e…2026-06-172023-05-202026-03-30940/75
6d1fe6ab3c… (payload)2026-06-072017-06-142026-07-105540/74

Every one is known, well detected, and repeatedly submitted over years by parties unrelated to me.

The first two columns are the finding. The host that reached my sensor on 30 June 2026 has been distributing the same file under the same name since 26 November 2019. Six years and seven months of continuous, unremediated infection. The other three have been running for three to five years.

Two smaller details sharpen the picture. The b4c8f6 sample had not been submitted anywhere since October 2025, so my capture is the most recent public sighting of that host. And d946db was submitted by someone else on 19 June, then reached me on 30 June, which places that machine in the middle of an active sweep.

The payload itself was first submitted on 14 June 2017, eight days after Dr.Web published its analysis of the family. It has been submitted 55 times since, most recently in July 2026.

A note on naming

Detection labels disagree, as they usually do. Dr.Web flags this file as Linux.MulDrop.16, VirIT as Linux.MulDrop.Q, Microsoft as Backdoor:Linux/IRCbot.YA!MTB, Symantec as IRC.Backdoor.Trojan. VirusTotal’s derived consensus is trojan.ircbot/shell. The family is commonly known as MulDrop.14 after the 2017 write-up, but the numbering has moved since, and the hash is the only stable reference.

What the wrapper is worth

Treated as intelligence rather than as noise, the SCP header supports four things.

It bounds the age of a compromise. The first-submission date of a wrapped hash is the first time any sensor anywhere caught that host, which places an upper bound on its infection date: the machine behind d946db was compromised no later than November 2019, and possibly well before. That is enough to separate a host compromised last month from one that has been spreading for years.

It clusters deliveries by source. Two captures sharing a wrapped hash came from the same infected host, even when the source IP is long gone. That matters more than it sounds: cowrie’s default log rotation is short, and mine had already discarded the sessions for four of these five deliveries. I do not know a single source address for them. I do know, from the four distinct wrapped hashes, that they came from four distinct machines rather than repeat visits by one.

It connects a local sighting to a public timeline. Because the artifact is byte-stable, an observation joins the record maintained by every other sensor that host has touched.

And it measures remediation. A wrapped hash still appearing years after its first submission is a machine nobody has cleaned.

Limits

The claim that one wrapped hash means one infected host is the reading best supported by the code, but it is not proven, and three alternatives deserve mention.

mktemp -u draws from a pseudo-random generator, and poor seeding on minimal embedded systems could produce name collisions between genuinely distinct hosts. A single long-lived machine behind a dynamic address would be indistinguishable from a stable one. And a host that reboots into the persistence path may or may not regenerate its name, a branch I have not exercised.

Confirming the mechanism means running the worm in an isolated lab and observing name stability across reboots and across simulated victims, which I have not done. There is also a ceiling on the public evidence: VirusTotal’s per-submission timeline sits behind the premium tier, so first-seen, last-seen and submission counts are all I can show. A full timeline of sightings spread across six years would carry more weight than two endpoints and a total.

A correction

I began this analysis convinced the SCP header broke detection. Unique artifact hash per delivery, reputation lookups coming back clean, threat feeds filling with indicators that would never match again. That was wrong on every count. All four wrapped samples score 39 to 40 detections, and vendors handle the wrapper without difficulty.

One piece of the original idea survives. Deduplicating by artifact hash does overcount: four files in my sample directory represented a single payload, and any figure for unique samples observed that is built on raw cowrie filenames will be inflated. Normalization before counting is still worth doing.

But the header is not something to strip and discard. Strip it to analyze the payload, and keep it to identify the source.

Indicators

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Payload, normalized (SCP header removed)
SHA-256  6d1fe6ab3cd04ca5d1ab790339ee2b6577553bc042af3b7587ece0c195267c9b

# Per-infection artifacts (wrapped), for tracking individual hosts
SHA-256  d946dbc57684f968666466909956619625332e869731f95d11152fd48d104fad  (active since 2019-11)
SHA-256  783565adc1ddbf9dab030a11f90d0c5897949504140a4c52458a3b7236f10486  (active since 2021-07)
SHA-256  b4c8f6e4e5ca7f71f5b94470d34880aa66d25bf88bbf405a0365ba3ef15db829  (active since 2021-10)
SHA-256  e63969f07eb117998329f37cb3543d83c76c1260d6122120ec7b7d256676b022  (active since 2023-05)

# C2 command-verification key (1024-bit RSA), SHA-256 of the DER encoding
02882f851be96034df935f231ebffa527eecb5af99278364a53364553c8a73ce

# Attacker key appended to /root/.ssh/authorized_keys (2048-bit RSA)
SHA256:8meXGfVBn7ruOmYMlZbuM8jqM42qeI9EYaDkFmSQX1Q
MD5:e6:29:2a:05:36:85:a4:2a:3a:4d:01:50:2e:7e:66:cb

Both keys are hardcoded in the payload and identical across all five deliveries. They identify the family rather than an individual host, which makes them the right indicators for fleet-wide hunting.

Takeaway

Honeypot captures carry more than the payload. Transport framing, control lines and filenames are all data the attacker produced without meaning to hand it over. Strip the header to analyze the payload, keep it to identify the source.


Samples were captured on a dedicated, isolated, disposable honeypot. Files of this kind should never be handled on a workstation. The payload is not reproduced here: it is functional and self-propagating.