Honeypot capture
redtail
An automatic pipeline recovered seven command-and-control endpoints statically from a 2,314-byte shell script captured by a honeypot sensor network, and all seven name the host 217[.]60[.]103[.]56. C2 is the channel an infected machine uses to reach its operator. In this sample the endpoints are stored as plaintext strings, each anchored to a file offset, and all seven carry an agreement verdict from the second engine that read the raw bytes in the consensus round. Those verdicts exist only because the full workflow was re-run from phase 5, after the fast path the case first took had skipped the consensus round.
The sample is a loader. A loader is a small first-stage program that determines what kind of machine it is running on, fetches the payload built for that machine, starts it and exits. It carries none of the capability the operator wants, which lives in the second stage it downloads. A loader is still useful to a defender, because it has to name the infrastructure it fetches from in a form the device can resolve. Recovering a loader therefore yields the addresses the operator used to deliver payloads in that wave.
This loader builds for five architectures — x86-64, i686, aarch64, armv7 and RISC-V — all served from one IP literal over HTTPS. The list is led by armv7, which points to a campaign aimed at routers, cameras and other embedded devices rather than at servers. That class of hardware is rarely patched and rarely monitored, and it stays online permanently. Because the endpoint is an IP literal, the infection reaches its server without DNS, and a single /32 carries the entire delivery channel for this sample.
The sample: a 2,314-byte bash script, attributed to redtail at confidence 0.75
Analysed 2026-09-20, static only; the file is not packed and was never executed.
| SHA-256 | 7c6c19b6e9343c772bda575a043220ec54942fe97118b51e13996d42ae21bd39 |
| SHA-1 | da4e997788ee208d85dfa92447a710f64e7007be |
| MD5 | 4a5991d504f80fe656fda665868b6911 |
| ssdeep | 48:vkwvpihneZZn1oBydXDA21j/EmfthmmzEvJGU6+gtIIqNTMTCki:vkwYheZZK88ujnFhmmzIl6K |
| File type | Bourne-Again shell script, ASCII text executable |
| Size | 2,314 bytes |
| Entropy | 5.2487, for a file in which a byte-class scan put all 2,314 bytes in the printable range |
| Family / type | redtail / dropper, loader (classification confidence 0.75) |
Finding somewhere to write and execute
Much of the script is given over to locating a directory that the current user can both write to and execute from. It reads the username with whoami, enumerates every mount flagged noexec with findmnt, or /proc/mounts piped through grep as a fallback, evidenced at 0x32b and 0x356, builds those mounts into an $EXCLUDE list, and runs a filesystem-wide find / at 0x424 for a directory owned by that user with u=rwx permission.
The string sweep found no analysis-evasion artifact of any kind, so a sandbox will run this sample the same way a real victim would. The one control the script evades is noexec hardening on the host, and it does so through two independent implementations, which indicates a deliberate design.
The script then probes each candidate directory twice, writing .testfile to test writability and .testfile2, a real 2 MB file written with dd and falling back to truncate at 0x50b, to test free space. Both are created and deleted in every candidate directory, and the 2 MB probe incidentally rejects small tmpfs mounts.
The script contradicts itself about /tmp, and the contradiction should carry into any hunt. /tmp is explicitly excluded from the find at 0x424, then appended unconditionally to the drop-directory loop list at 0x4de, so when the search returns nothing it is used anyway, alongside /var/tmp and /dev/shm.
The script renames the payload before running it to a literal dot followed by 4 to 35 random alphanumerics, computing the length as urandom % 32 + 4 at 0x4bc with a three-tier CSPRNG that prefers openssl rand -base64 256 at 0xab and falls back to /dev/urandom. The dropped file has no stable name that a signature could match.
The openssl call is a likely false positive for cryptography in a sample of this kind. It has one consumer, FILENAME= at 0x4bc, which strips its base64 output to [A-Za-z0-9] with tr -dc and truncates it with head -c, and it stops there, well short of the network line at 0x286, so the sample implements no cryptography of its own and holds no key material.
Seven endpoints, one host, no domain name
All seven recovered endpoints name one host Values defanged. Offsets are file offsets in the 2,314-byte script, which as an interpreted script has no virtual address space. Verified means an agreement verdict from the second engine that read the raw bytes in the consensus round.
| Value | Port | Role | Offset | Conf | Verified |
|---|---|---|---|---|---|
217[.]60[.]103[.]56 | 443 | primary | 0x286 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/clean | 443 | primary | 0x5d4 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/x86_64 | 443 | primary | 0x681 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/i686 | 443 | primary | 0x6d5 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/aarch64 | 443 | primary | 0x745 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/arm7 | 443 | primary | 0x797 | 0.95 | ✅ |
hxxps[://]217[.]60[.]103[.]56/riscv | 443 | primary | 0x7e3 | 0.95 | ✅ |
Port 443 is an inference from the https:// scheme; no port appears anywhere in the file. The script spells the path arm7 where armv7 is more usual, and its RISC-V support is unusual and narrows the family.
The entire network layer is two lines, the function dlr() at 0x272. It issues wget --no-check-certificate -q, and on failure falls through || to curl -skO. Both branches switch certificate validation off, which suggests the operator holds no valid certificate for a bare IP. Both invocations omit -A, --user-agent and --header, so the on-wire request signature belongs to whichever wget or curl build is installed on the victim. A detection rule built from this sample therefore has nothing to key on but the destination address, because the victim’s own installed tooling produces the HTTP request.
clean is downloaded and run with sh at 0x5ed and then deleted, a prior-infection marker .redtail is removed at 0x614, uname -mp at 0x4a2 selects one of five architecture URLs, and the download is chmod +x’d and run as ./$FILENAME $1 at 0x87d and 0x8e6. On an unrecognised CPU the fallback at 0x807 fetches and runs all five in turn, deleting each one it tries. The chain ends when the script runs the file it has downloaded.
The pipeline searched the fully mapped file by name for the features an implant would show next — a beacon, a polling interval, a command dispatcher — and found none. The beacon sweep sleep\|while true\|until \|case .* in\|read returned no hit, r2 -e search.in=raw -c '/ck' found no cipher constant table, and a byte-class scan put all 2,314 bytes in the printable range, so no region of the file is encoded.
The script forwards its own $1 to the second stage at 0x87d and 0x8e6 but never defines it. A campaign tag, referral ID or config selector therefore exists outside this file, held by whatever invokes the dropper, and its value is not statically recoverable.
Most of what it writes, it deletes again
Each row is a single-source reading of plaintext at the stated offset, outside the consensus round’s claim vocabulary and therefore unverified.
| Kind | Value | Context | Offset | Conf |
|---|---|---|---|---|
| file | .redtail | prior-infection marker, deleted before the new drop; its presence indicates an earlier infection by this family | 0x614 | 0.85 |
| file | clean | first-stage payload, fetched, run with sh at 0x5ed, then deleted | 0x5d4 | 0.95 |
| file | .[A-Za-z0-9]{4,35} | pattern for the dropped architecture payload | 0x4bc | 0.90 |
| file | .testfile / .testfile2 | writability and 2 MB free-space probes, per candidate directory | 0x50b | 0.90 |
| directory | /dev/shm | drop-directory fallback; memory-backed, so it leaves nothing on persistent storage | 0x4de | 0.90 |
| directory | /var/tmp, /tmp | drop-directory fallbacks | 0x4de | 0.90 / 0.85 |
The random filename is an indicator only as a pattern: any specific name is per-infection and must never be shared as an IOC.
The script deletes stale downloads at the head of dlr(), the probes, clean after running it, the marker, and each architecture payload tried in the fallback loop, and it redirects all three execution sites with >/dev/null 2>&1, which leaves little residue on disk or console.
No cron, systemd, .bashrc, rc.local or ld.so.preload string appears anywhere in the 2,314 bytes, so this sample implements no persistence; if the family has any, the downloaded payload implements it.
The pipeline attributes the sample to redtail at confidence 0.75 on two attacker-authored strings, the literal token redtail at 0x253 and the .redtail marker at 0x614. Those strings are text the attacker typed, and the file holds no mining pool, wallet or stratum:// string, so mining, DDoS or some other objective is a question for the payloads.
Hunting actions and the six payloads not yet held
This sample runs given only a user account, a shell, wget or curl, coreutils and outbound traffic to port 443 at one address. Block the /32, hunt for .redtail and for dot-prefixed executables matching .[A-Za-z0-9]{4,35} in /tmp, /var/tmp, /dev/shm and user-writable directories, and treat any host that fetched from the address as compromised, since the dropper executes what it downloads without validating it.
Acquiring and analysing the six payloads out of band would settle whether the family mines, runs DDoS or does something else; they are clean, x86_64, i686, aarch64, arm7 and riscv, served from hxxps[://]217[.]60[.]103[.]56/.
- Family
- redtail
- First seen
- September 20, 2026
- Vector
- Runs as a dropper/loader; the entry vector is not established by static analysis
- Format
- 2 KB Bourne-Again shell script
- VirusTotal
- 29/74 engines: trojan.sagent/shell
- Tags
- dropper · https · loader · script
- Sample
- By request. Email security@kinryu.sh
SHA-256
-
7c6c19b6e9343c772bda575a043220ec54942fe97118b51e13996d42ae21bd39as captured
Analysis performed using an automatic malware analysis pipeline using Binary Ninja