Series navigation
- Part 1/3 - Catching Sorry-worm in the wild: discovery and propagation pattern
- Part 2/3 - Inside Sorry-worm: anatomy of a Go ransomware-worm hybrid (this article)
- Part 3/3 - Adjacent campaigns and a defender’s playbook
This second part of the series is the binary-level analysis. We assume the campaign-level context from Part 1
; if you arrived here directly, the short version is that Sorry-worm is a previously undocumented Linux ransomware-worm hybrid we observed propagating in the wild within hours of its first public sandbox submission. Microsoft detects it on VirusTotal as Ransom:Linux/Multiverze!rfn; we treat this as an automated umbrella-style classifier label around Linux SSH-bruteforce/ransomware behavior, not as evidence of shared code or operator identity with the long-standing Multiverze sshd backdoor family.
The single most important behavioral property of this binary is that encryption and SSH propagation occur concurrently in the same process. Most Linux ransomware variants finish encryption first, then either exit or hand off propagation. Sorry-worm does both at once. From a defender’s perspective, by the time encrypted files are detected on disk, propagation traffic is already in flight on port 22.
1. Sample metadata
| Attribute | Value |
|---|---|
| SHA-256 | 2fc0a056fd4eff5d31d06c103af3298d711f33dbcd5d122cae30b571ac511e5a |
| MD5 | 01896fbb58e8edefc5a8392e467c2260 |
| SHA-1 | 0827b2893ea31c1dd307ac4d465edba631afa845 |
| Size | 5,329,044 bytes |
| Format | ELF 64-bit, x86-64, statically linked, stripped |
| Compiler | Go (toolchain ≥ 1.24, inferred from standard library presence - see §6) |
| Build tells | Symbol stripping with retained Go type names; randomized type names and source filenames |
| Detection at our analysis | 6 detections on VirusTotal (at the time of analysis) |
| First public submission | 2026-04-30 (Triage 260430-w1vvkact8m, tria.ge ) |
| Microsoft label | Ransom:Linux/Multiverze!rfn (observed on VirusTotal - see §1.1 for treatment) |
| Filenames observed in the wild | pic.ico (Triage submission), sshd (VirusTotal metadata), /tmp/.sorry_<8 random alphanumeric chars> (our SSH-drop telemetry) |
| TLSH | T187364AC7FC9049A5C0AEA33589629253BA717C486F3023D76B50FF282F76BD06A79354 |
The detection rate at triage is the operationally significant number. A 5 MB statically linked Linux ELF that performs active ransomware encryption and concurrent SSH propagation, classified as malicious by only six engines on VirusTotal, sits well below what a signature-based pipeline catches on its own. The TLSH and SSDEEP fuzzy hashes are listed for fuzzy matching against future variants.
1.1 A note on the Microsoft label
Microsoft detects this binary on VirusTotal as Ransom:Linux/Multiverze!rfn. We treat this as an automated umbrella-style classifier label around Linux SSH-bruteforce/ransomware behavior - the suffix !rfn indicates an automated verdict rather than analyst-assigned attribution - not as evidence that Sorry-worm shares code or operator identity with the long-running Multiverze sshd backdoor family discussed in Part 3 §1.1
. Both are detected under the Multiverze umbrella because both exhibit Linux-SSH-bruteforce-related behavior; that does not, on its own, make them the same family or the same operator.
2. Naming convention versus the 2018 Windows Sorry family
The .sorry extension and the sorry_id_<digits>.sorry victim-identifier filename used by this binary echo a 2018 Windows ransomware family documented by pcrisk under the name “Sorry”. We want to be explicit: this Linux sample shares no code, no infrastructure, no ransom channel, and no targeted platform with that 2018 Windows family. What it shares is English vocabulary on the file extension and the ID file naming convention.
The 2018 Sorry was a Windows AES-based file encryptor delivered in the Hidden Tear lineage, with a How Recovery Files.txt ransom note and an ID_QA6YQYTI2F-style victim identifier delivered by email. The Linux Sorry-worm we describe in this report is a Go-compiled binary using AES-CBC for file content with RSA-2048 wrapping of per-file keys, a TOX/qtox ransom channel, no email, no web portal, and an embedded SSH propagation module. The technical implementations are unrelated.
We mention the naming overlap to inoculate readers against assuming the two are connected. Our naming convention “Sorry-worm” for the present sample is meant to disambiguate from that 2018 family while preserving the visible filesystem indicator that defenders will see first.
3. Crypto stack and the RSA pubkey as an attribution-stable indicator
Strings extraction from the binary identifies the cryptographic primitives in use:
- AES-CBC for file content, via Go standard library (
crypto/cipherNewCBCEncrypter). - RSA-2048 with public exponent
e = 65537, used to wrap the per-file AES keys. The RSA public key is embedded in the binary as a PEM-encoded PKCS#1 block (header-----BEGIN RSA PUBLIC KEY-----). A separate OpenPGP-Public-Key-Packet–style framing appears as the 48-byte fixed prefix prepended to every encrypted file (see §4). - chacha20, chacha8, ML-KEM-768, and BLAKE2b are also present in the binary, but as artifacts of the Go toolchain ≥ 1.24 standard library - the binary does not appear to use them in its primary encryption path. Their presence is a useful build-fingerprint, not a behavioral indicator.
The hardcoded RSA-2048 public key is the most important attribution-stable indicator for this campaign. As long as the operator continues to use the same key pair to wrap victim AES keys, every encrypted file produced by this operator across every victim and every variant of the binary is decryptable only by the holder of the matching private key - and contains material derived from the same public key.
| RSA public key indicator | Value |
|---|---|
| SHA-256 (DER public) | 02cffd86bcfae828ca5cdea65b794a47079f49cc52c72b32570ed5abff24fd99 |
| SHA-1 (DER public) | de674a2d43a3a3aa6e53744060ae39be599eb4b7 |
| Modulus length | 2048 bits |
| Public exponent | 65537 |
If a future variant of the binary appears with a different SHA-256 and different filesystem markers but the same RSA public key fingerprint, it should be treated as the same campaign. Conversely, an apparently identical binary with a different RSA public key should be treated as a fork, a copycat, or a separately-keyed deployment by an unrelated operator.
4. The 48-byte fixed prefix on encrypted files
Every .sorry file we observed begins with the same 48-byte sequence:
99 00 00 08 00 6d 02 1b c7 93 e1 23 d5 af 31 12
e8 32 c3 de 6d 40 d1 0e 1a 57 1c de df 93 e7 8e
ca d8 03 e6 56 fe 9f 9f 92 e6 43 0b 1c 12 f6 f0
SHA-256 of the prefix: 5bfbdd128ceef2d6820897f1af9cc4060e9c53cd5042dc4b3cf6de0a9f58af82 (also tabled in Part 3 §2.1
).
The first byte 99 and the framing bytes 00 00 08 00 correspond to an OpenPGP Public-Key Packet header (old-format packet tag for “Public-Key Packet”, with a 2048-bit RSA modulus length encoding). The bytes that follow are consistent with the leading bytes of the embedded RSA-2048 modulus formatted as an OpenPGP packet - i.e., the encrypted file is prepended with a fragment of the same RSA public key the binary holds in memory.
This has three useful consequences for defenders:
- The prefix is identical across all
.sorryfiles produced by this campaign, regardless of victim, file type, or file size, because it depends only on the embedded RSA key - not on the victim or the file content. - The prefix is a high-confidence YARA-grade indicator for this campaign specifically. Since it mirrors the campaign’s RSA public key, a different operator using a different key would produce a different 48-byte prefix.
- The prefix mimics OpenPGP Public-Key Packet structure, which means file-magic scanners and naive content classifiers may mis-classify encrypted files as inert PGP key material. Defenders running content inspection should treat OpenPGP-Public-Key-Packet–leading files in user data directories as suspicious until proven otherwise.
A YARA rule using this prefix is provided in Part 3 .
5. The victim-ID file: a UNIX-nanosecond timestamp encoded in plain sight
Sorry-worm writes a per-victim identifier file to disk at the start of its runtime. The filename follows the pattern:
sorry_id_<19-digit decimal>.sorry
The 19-digit decimal portion is the UNIX timestamp of the victim-ID write event, expressed in nanoseconds since the epoch.
In one of the events we observed, the file was named sorry_id_1777620100440138987.sorry. Decoding the integer:
1777620100440138987 ns
= 1777620100 s + 440138987 ns
= 2026-05-01 07:21:40.440138987 UTC
This timestamp is exactly 66 seconds after the binary’s execve time of 2026-05-01 07:20:34 UTC, and falls inside the encryption window that ended at 07:21:55.911 UTC. The victim-ID is therefore written approximately 15 seconds before encryption completes - early enough in the runtime to predate the ransom note write attempt, late enough to occur after the binary has self-unlinked from disk.
For defenders this means the filename of the victim-ID file alone is sufficient to date the infection to the nanosecond, with no need for log correlation or live runtime capture. The decoder is one expression:
import datetime
def decode_sorry_id(fname: str) -> datetime.datetime:
"""Decode a Sorry-worm victim-ID filename to a UTC datetime.
Example: 'sorry_id_1777620100440138987.sorry'
→ datetime(2026, 5, 1, 7, 21, 40, 440138, tzinfo=UTC)
"""
digits = fname.split("sorry_id_", 1)[1].split(".sorry", 1)[0]
n = int(digits)
secs, ns = divmod(n, 1_000_000_000)
dt = datetime.datetime.fromtimestamp(secs, datetime.timezone.utc)
return dt.replace(microsecond=ns // 1000)
The 2.7 KB blob inside the file appears to be the per-victim wrapped material the operator would request alongside the TOX-channel handshake described in §7. We do not publish its contents.
The runtime artifact is convenient for incident response: a SOC team picking up a sorry_id_<digits>.sorry file in a user home directory weeks after the infection can reconstruct the exact infection time without needing to recover the binary or its log. Conversely, an operator who shares the victim-ID with a ransom recipient is effectively timestamping their own activity to the nanosecond - a small operational mistake that makes cross-incident correlation easier for analysts.
6. Build pipeline tells
Several artifacts in the binary point to a deliberate build pipeline rather than off-the-shelf tooling:
- Go static linking with strip: standard for malicious Go ELFs, but the binary retains Go type information sufficient to recognize standard library imports.
- Randomized type names: structures that should normally have descriptive names (under regular Go compilation) are renamed to identifiers like
*main.ewGSDj,*main.l3F_VWzg8x4Q,*main.CuyV7jq5lHH. This is consistent withgarble-style or custom Go obfuscators that rewrite identifiers post-parse. - Randomized source filenames: at least 30 distinct apparent source files are referenced by the runtime as random tokens (e.g.
JtoXJAoTwVD.go,H0qal8ca0a6s.go,T8K5UzKu.go). This is consistent with a pipeline that hashes-or-tokens source filenames to defeat simple grep-on-strings classification. -trimpathbuild flag is implied by the absence of file path leakage in Go runtime errors that would normally include build host paths.
The combination is not novel - multiple Go malware families adopt similar pipelines - but it is reproducible, which means defenders can build YARA rules against the persistent indicators (RSA public key, 48-byte file prefix, runtime markers) rather than against the obfuscated identifier soup.
A characteristic Go runtime log timestamp format with microsecond precision is observable in the runtime log fragment we recovered:
2026/05/01 07:21:40.897012 …
This is the default log package timestamp format with Lmicroseconds enabled, which combined with the static link and the obfuscated identifiers is a high-confidence Go fingerprint.
7. The ransom channel: TOX/qtox with a taobao.com fallback
Strings extraction recovers the ransom note template embedded in the binary:
Please contact us through the qtox tool
Download qtox https://github.com/qTox/qTox/blob/master/README.md#qtox
If you can't contact us, please contact some data recovery company(suggest taobao.com),
may they can contact to us.
Add our TOX ID and send an encrypted file and 'Sorry-ID' for testing decryption.
Our TOX ID: %v
-----BEGIN RSA PUBLIC KEY-----
…
1QIDAQAB
-----END RSA PUBLIC KEY-----
The %v placeholder for the TOX ID is filled in at runtime, meaning the same binary can be re-keyed with a different operator TOX endpoint without recompilation. The actual TOX ID used in our observed events is not exposed in the binary’s static strings; it is loaded at execution time. We do not publish either the binary’s embedded RSA public key in cleartext nor any operator TOX address that may be observed in the wild - the public-key fingerprints in §3 are sufficient for indicator hunting.
Two points worth noting on the ransom channel:
- TOX (qTox) is a peer-to-peer messenger: there is no centralized DLS, no negotiation portal, no email handle. This is consistent with an operator favoring decentralized contact infrastructure that resists takedown. It also means there is no public victim list to scrape.
- The
taobao.comfallback - suggesting that victims contact a Chinese e-commerce data-recovery service if they cannot reach the operator on TOX - is unusual and worth flagging. We do not assess attribution on this signal alone, but it is consistent with an operator orienting toward the Chinese-language data-recovery ecosystem as a backstop.
For the actual ransom note as it would be left on a victim system, Triage’s sandbox analysis
on an Ubuntu 18.04 host without specific hardening recorded the binary creating cron jobs and writing into /usr/bin, alongside its file encryption activity - i.e., on a host that allows them, the persistence and note-writing operations succeed. In one observed runtime instance, no on-disk ransom note was produced; the encryption pass and the SSH worm leg ran to completion regardless.
8. Encryption behavior
In a single observed cycle, Sorry-worm encrypted 241 files across 296 directories in approximately 15.47 seconds, with 33 files skipped. Encrypted files have the .sorry extension appended to the original filename and begin with the 48-byte prefix described in §4. The encrypted block format is otherwise consistent with a sequence of AES-CBC blocks following the prefix.
A burst of encryption activity captured during runtime shows roughly 16 files per second sustained, with occasional 400-millisecond windows of 33-file bursts during which files are processed in parallel. This concurrency is consistent with the binary using a worker-pool pattern over the Go scheduler.
8.1 Targeted file extensions
The binary embeds a list of approximately 86 file extensions targeted for encryption. Without publishing the full list, the categories are:
- Office and document formats - including legacy and OOXML formats.
- Image and media formats - common consumer image and video extensions.
- Archive formats - including
.zip,.rar,.7z, and other compressed archive types. - Database files - including
.db,.sql,.kdbx(KeePass),.ibank(iBank), and others. - Virtual machine disk images - including
.qcow2(QEMU) and.vmdk(VMware). - Source code and project files - common programming language extensions and project metadata.
- Backup and dump formats -
.bak,.dump, and similar.
The presence of .kdbx and .ibank in the target list is operationally significant: these formats indicate the binary deliberately targets credential and financial data alongside generic user files.
8.2 Skip paths and Windows heritage
The binary explicitly skips a list of paths and filename patterns. Most are Linux-appropriate (/boot, /proc, /sys, /lost+found, .cache, __pycache__, node_modules, .trash). However, the skip list also contains entries that have no meaning on a Linux target host:
recycle.bindesktop.iniautorun.inficoncache.dbbootfont.binthunderbird(with Windows-style profile pathing)birdconfig
The presence of Windows-only path filters in a Linux ELF strongly suggests this code was forked from, or shares an extension/skip-path wordlist heritage with, a Windows ransomware codebase that has been ported to Linux while retaining its original target list. We do not assess this as direct evidence of relation to the 2018 Windows Sorry family in particular, since the heritage may come from any of several Windows ransomware projects whose extension lists circulate in the underground.
8.3 Process kill list
Before beginning encryption, the binary enumerates running processes and kills those whose executable name matches an embedded list. The list covers, at minimum:
- Database engines:
mysql,mysqld,mongod,mongos,oracle,redis-server - Office productivity:
excel,mspub,visio - Browser configuration helpers:
firefoxconfig - Mail clients and agents:
agntsvc,encsvc,thebat - Other long-lived consumer processes:
steam,snmpd,ocomm,ocssd
The binary’s runtime log includes the format string error killing database processes: %v, suggesting kill failures are logged but not blocking. As with the skip-path list, the presence of Windows-named processes (excel, mspub, visio, agntsvc, encsvc, thebat, firefoxconfig) inside a Linux ELF reinforces the observation that the kill list is reused from a Windows codebase.
9. The concurrent SSH worm leg
While encryption is running, the binary is simultaneously performing SSH brute-force scanning. The runtime log captures four expanding scan layers:
| Layer | Target count |
|---|---|
| 1 | 5 |
| 2 | 254 |
| 3 | 249 |
| 4 | 129,540 |
In parallel, the process holds approximately 47 open file descriptors of socket type during active scanning. The combined behavior - local file rewrite plus high-volume outbound TCP/22 fan-out from the same PID - is, on its own, a behavioral signature worth alerting on independent of any sample-specific identifier.
9.1 Embedded password wordlist
The binary embeds a small static SSH password wordlist:
123456, ubuntu, deploy, tomcat, qwerty, abc123, 111111, master, monkey, dragon
It also generates per-target candidates by templating the discovered username:
{{user}}
{{user}}@123
{{user}}1234
{{user}}2024
{{user}}2025
{{user}}2026
{{user}}123
{{user}}!@#
The presence of {{user}}2024, 2025, and 2026 strongly suggests the wordlist or the templating logic was last refreshed during 2026, which is consistent with the sample’s first public submission date.
9.2 Successful login format
The runtime log includes the format string for a successful SSH login:
[+] SUCCESS %s:%d user=%s pass=%s
and a separate variant with key=%s for cases where authentication succeeds via a hijacked key rather than a guessed password. Defenders observing log entries matching this format on a system where Sorry-worm is suspected to have run can recover the lateral-movement target list directly from the log (provided the log file descriptor has not been unlinked - see §10).
9.3 Why concurrency matters
The sequencing of encryption and SSH propagation is the property that distinguishes Sorry-worm from most Linux ransomware variants we have observed. By running both legs concurrently in the same process, the binary:
- shortens the total impact-to-propagation window to zero;
- prevents containment-by-isolation strategies that rely on detecting encryption first and then quarantining the host;
- propagates from a host that may already be subject to incident response by the time the propagation is noticed.
A SOC playbook that quarantines a host on .sorry-extension detection has already lost the lateral movement window: by the time the file extension is visible on disk, Sorry-worm has already opened tens of sockets to layer-4 candidates.
10. Self-deletion, mutex, and runtime markers
Sorry-worm leaves several runtime artifacts on disk, most of which are unlinked shortly after creation while remaining mapped in the running process.
| Artifact | Purpose |
|---|---|
/tmp/.sorry_<8 random alphanumeric chars> | The binary itself, dropped before execution and unlinked after execve. |
/tmp/.sorry_<8 random alphanumeric chars>.log | Runtime log capturing encryption stats, scan progress, and SSH success entries. Unlinked at runtime, kept open via fd 1 and fd 2. |
/tmp/Sorry.lock | Runtime lock file, unlinked while held. Prevents multiple concurrent runs. |
/tmp/.sorry_exist | Zero-byte mutex marker indicating “encryption already happened on this host”. On-disk, not unlinked. |
/tmp/.<8 random lowercase chars> (42 bytes) | Tracker file storing <PID>\n<path-to-current-payload>, observed on the same target host during the Sorry-worm runtime window. Family attribution is uncertain: the specific instance whose contents we recovered mapped to a Multiverze sshd backdoor PID, not to the Sorry-worm process - see Part 3 §1.4
. |
sorry_id_<19-digit nanoseconds>.sorry | The victim-ID file described in §5. |
10.1 The .sorry_exist mutex
The presence of a zero-byte file at /tmp/.sorry_exist is, on its own, a high-confidence indicator that Sorry-worm has executed on the host. Because the file is empty, it has the universal SHA-256 of the empty file (e3b0c44…); the indicator is the path and existence, not the hash.
For defenders this is the most compact post-execution artifact: a single find /tmp -name '.sorry_exist' -size 0 -type f is enough to flag the host as having seen Sorry-worm even if the encrypted files have been recovered or deleted.
10.2 The unlinked-but-mapped pattern
The binary, the runtime log, and the lock file are all unlinked from the filesystem within seconds of execution while remaining mapped in the running process. Standard live triage tools that walk the filesystem will not find them. The behavior of the running process can only be reconstructed through process memory recovery: reading the file objects that the kernel still holds open through the running process. During analysis, this is how the executable was retrieved and how its runtime log was reconstructed.
For defenders running on a host where Sorry-worm is suspected to be running:
- a process whose
/proc/<pid>/exesymlink resolves to a path ending in(deleted)- and the deleted path matches/tmp/.sorry_*- is a high-confidence in-memory indicator; - the runtime log can be read directly from the running process’s
fd/1orfd/2; - killing the process before recovery destroys the only on-host copy of the log.
11. Detection seams summarized
Despite the obfuscation pipeline and the unlink-after-exec pattern, Sorry-worm leaves several stable detection seams:
- Pre-execution: SSH-exec session pattern matching
chmod +x /tmp/.sorry_*followed bynohup /tmp/.sorry_* >/tmp/.sorry_*.log 2>&1 &is a high-confidence pre-encryption signature. - During execution: a single PID with concurrent local file rewrite activity and high-volume outbound TCP/22 fan-out, where the executable path under
/proc/<pid>/exeresolves to a deleted/tmp/.sorry_*file. - Post-execution: the
.sorryextension on user files; thesorry_id_<19-digit>.sorryvictim-ID file (decodable by the script in §5 ); the/tmp/.sorry_existzero-byte mutex; the 48-byte fixed prefix on encrypted files. - Cross-incident: the RSA public key SHA-256 fingerprint (
02cffd86…) is the most stable attribution indicator and survives binary recompilation, repacking, and symbol re-randomization.
Concrete YARA, Sigma, and hunting queries are provided in Part 3 - Adjacent campaigns and a defender’s playbook , alongside the indicators tabled in three confidence tiers.
OHIIHO Research - Independent threat research. Contact: cyber@ohiiho.com .