What Failed Commands Reveal:
Failed-Intent Analysis Across 81,000 SSH Sessions
When a botnet's Stage 2 looks for a tool it expected to find and gets command not found, the honeypot captures an implicit assumption the attacker never meant to expose. This is a population-level analysis of failure patterns across 81,341 sessions and four nodes - not what attackers did, but what they assumed they could do.
Honeypot data is typically analyzed for what attackers succeed at: credentials tried, commands executed, payloads downloaded. The failure surface is harder to read because a failed command looks like noise. Over time that noise accumulates into a signal - a map of what the attacker's mental model assumed about your system.
We built a Failed-Intent Layer as part of the NullRoute analysis framework, running across the current DuckDB session corpus. The engine classifies failed commands by intent category - tool invocation, interactive abort, environment probe, payload retrieval - and computes a frustration score per session. What came back was not random noise. It was a structured, repeating pattern tied to specific points in the Dota mdrfckr kill chain.
The Dominant Failure Patterns
Across 2,457 sessions containing at least one failure, four patterns account for the vast majority. Each maps to a distinct assumption the attacker made about the target environment. Because a single session can contain multiple failure types, these counts are not additive.
Bars show sessions containing each pattern. A long tail of low-frequency or currently unclassified failure patterns accounts for the remainder.
The distribution is heavily skewed. lockr appears in the most failure sessions
by a wide margin. The other patterns are secondary but analytically distinct. Together they
suggest a coherent picture of Dota's kill chain architecture and its assumptions about target state.
lockr: A Tool That Was Never There
The most frequent failed command across the entire session database is this:
lockr -ia .ssh
lockr is not a standard Linux binary. It does not exist in Cowrie's default filesystem.
It does not appear in any known Linux distribution package index. The flag syntax - -ia
on a directory target - is structurally identical to chattr -ia .ssh, which removes the
immutable and append-only extended attributes from the .ssh directory, allowing its
contents to be modified even if another process has locked them.
2,277 sessions called this binary. All 2,277 failed. The tool was not there in any of them.
This is not a Cowrie emulation gap. Cowrie faithfully emulates a large set of standard Linux
utilities. If an attacker calls a tool that Cowrie doesn't know about, Cowrie returns a
command not found response and logs it as a failed command. The question is not why
Cowrie can't run lockr - the question is why 2,277 Dota sessions expected
it to be there.
The Stage 1 - Stage 2 Dependency
Dota mdrfckr operates as a two-stage kill chain. This was documented in earlier NullRoute research: Stage 1 sessions are short (median under 5 commands), inject an SSH key, and establish an entry point. Stage 2 sessions return on that key and execute the full payload - a sequence of 15-25 commands that includes mining setup, persistence via cron, SSH directory manipulation, and process management.
The key observation: lockr -ia .ssh appears exclusively in Stage 2
sessions. Not in the 3-command Stage 1 abort sequences. In Stage 2 sessions,
lockr is called after the attacker has already authenticated and is mid-way
through the kill chain - at the point where they would normally remove SSH directory locks
before writing new keys.
The most plausible explanation is that lockr is expected to exist on hosts
that have already passed through an earlier compromise step. One candidate is Dota's Stage 1:
on a real target, Stage 1 executes successfully, its payload runs, and among the files it
drops or downloads may be a binary called lockr. Stage 2 arrives, expects the
binary in place, and calls it. Passive honeypot data alone does not prove when or how the
binary is introduced - it could also come from a separate tooling step or a prior campaign.
A honeypot does not persist payloads between sessions. Whatever Stage 1 dropped is gone.
Stage 2 calls lockr, gets command not found, and continues anyway -
the broader kill chain is not blocked by this step. The failure is silent from the attacker's
perspective. It is only visible because the honeypot logged it.
What this suggests: Dota's Stage 2 workflow appears to expect a binary
named lockr to be present, but the kill chain is not fully gated on it.
Detecting lockr in SSH session command logs - whether successful or failed -
is a high-priority hunting lead for Dota Stage 2 activity. A file named lockr
on disk would be consistent with prior compromise staging, especially when correlated with
SSH key modification or follow-on Stage 2 commands.
What lockr Probably Does
We can infer the likely function from context. chattr -ia .ssh is a well-documented
technique used by SSH worms to remove immutable file attributes from the .ssh
directory before modifying authorized_keys. Some defenders, and some competing
malware, set chattr +ia on .ssh to prevent key injection. A custom
wrapper named lockr that mirrors this interface is consistent with a tool used
for SSH directory attribute manipulation before key changes.
Why use a custom binary rather than calling chattr directly? Two possibilities:
evasion (custom binary name avoids process-based detection of chattr in SSH sessions)
or portability (a statically linked binary that works even when chattr is missing,
restricted, or replaced). We cannot determine which from honeypot data alone.
Observed: lockr -ia .ssh in 2,277 sessions, all failed in Cowrie.
Inferred: Expected host-side tool dependency, likely for SSH directory attribute manipulation.
Unknown: Exact binary function, delivery path, prevalence on real targets.
passwd_change: Locking Out the Competition
The second-most-common failure pattern is different in character. 1,312 sessions reached the
point of attempting a password change on the compromised account - and stopped, because Cowrie
responds to passwd with an interactive prompt that the attacker's scripted session
does not handle.
# Cowrie's passwd response Enter new UNIX password: <-- attacker's script has no handler for this # Session aborts here
On a real target, changing the root password after compromise is a well-documented post-exploitation step - not for persistence, but for competitive exclusion. A compromised server with a changed root password can no longer be accessed by the next attacker using the original credentials. Dota attempts this in 1,312 sessions, which suggests password rotation is a recurring post-compromise behavior in a substantial subset of Dota sessions.
Combined with the SSH key injection in Stage 1 (which provides Dota with its own persistent access that does not depend on the original credentials), the password change step serves a clear purpose: secure the foothold against other credential-bruteforcing actors.
Cross-Node Frustration: Same Botnet, Different Experience
The failed-intent engine computes a per-session frustration score (failure-to-command ratio): the proportion of failed commands relative to total commands. When broken down by node, a significant asymmetry emerges in how Dota experiences the two Cowrie deployments.
Sessions with failures: 14
Typical session length: 3 commands
Stage reached: Stage 1 (abort)
Sessions with failures: 98
Typical session length: 19 commands
Stage reached: Stage 2 (mid-chain)
The 6x difference in frustration score reflects a structural difference in what Dota encounters on each node - not just a difference in how much it fails.
On Node 2 (Healthcare, auth_class = UserDB), Dota's Stage 1 fails at the
shell-script execution step. Sessions are 3 commands long: the attacker sends
if [ [ ! -d ${HOME}/.ssh ] ], then, and fi as
individual commands. Cowrie doesn't execute multi-line shell constructs as a unit - each line
is treated as a separate command, the conditional never evaluates, and the session aborts.
The 46.4% frustration score reflects sessions where 1-2 of 3 commands fail immediately.
On Node 3 (AI/ML, previously auth_class = AuthRandom with open authentication),
Dota reaches Stage 2. Sessions are 19 commands long. The frustration is lower per-session
because most commands succeed - but the one that fails, lockr -ia .ssh, is
a structural dependency, not a recoverable error.
Both nodes run the same Cowrie version. The observed difference is consistent with auth mode and stage distribution being major drivers, though the comparison spans different node configurations and historical periods. We focus on Node 2 and Node 3 here because they show the clearest divergence in failed-intent profiles; Node 1 and Node 4 contributed fewer comparable Dota failure sessions in the selected window.
Scope note: Node 3 data spans a period when authentication was set to AuthRandom (open). The high Stage 2 session rate on Node 3 reflects that era, not the current UserDB configuration deployed on 2026-04-03. Cross-node comparisons are valid for the historical period; current Node 3 data is still accumulating.
The Environment Probe: Checking for Freshness
A smaller but distinct pattern appeared in 48 sessions: a probe against /proc/uptime
via this exact command sequence:
cat /proc/uptime 2 > /dev/null | cut -d. -f1
The output is the system uptime in integer seconds. A plausible interpretation is environment triage: checking whether the host appears freshly booted, ephemeral, or otherwise atypical before proceeding. This may overlap with sandbox-avoidance logic - a freshly booted system is more likely to be an analysis environment than a production server running for months - but the command alone does not prove that intent.
All 42 probes failed because of a syntax issue: 2 > /dev/null should be
2>/dev/null. The space before the redirect operator causes Cowrie to treat
2 as a command argument rather than a file descriptor. The spaced form is
malformed for standard shell usage and fails in Cowrie. Without reproducing the exact syntax
across the shell environments relevant to this campaign, we avoid stronger claims about
whether this would also fail on all real targets.
Behavioral Actor Resolution
Within the Dota-labeled population, actor clustering surfaced two especially salient groups with very different characteristics.
| Cluster | IPs | Sessions | Nodes | Active | Cohesion |
|---|---|---|---|---|---|
| ACTOR-13A077 | 279 | 12,292 | node1-de, node2-us, node3-fr | 19 days | 0.869 |
| ACTOR-329E47 | 6 | 9,414 | node2-us, node3-fr | 5 days | 1.000 |
ACTOR-13A077 is the familiar wide-distribution Dota cluster: 279 exit IPs, active across three nodes over 19 days, high but not perfect behavioral cohesion (0.869). The slight cohesion gap reflects variant drift across the IP pool - some IPs run the full kill chain, others truncate it.
ACTOR-329E47 is more unusual: 6 IPs generating 9,414 sessions over 5 days. That is roughly 1,569 sessions per IP - an order of magnitude higher than typical Dota scanning rates. The cohesion of 1.000 means every session from every IP in this cluster executes an identical command sequence. The perfect cohesion suggests a tightly controlled cluster using identical tooling or orchestration. Possible explanations include a small VPS pool, centrally managed exits, or proxy/VPN rotation, but passive honeypot telemetry alone cannot distinguish between them.
ACTOR-329E47 operates only on node2-us and node3-fr - not on node1-de (which runs T-Pot with a different authentication model). This is consistent with the cluster targeting systems where its specific credential list succeeds, rather than scanning broadly.
Detection Implications
The failure patterns each offer hunting leads on real targets, not just honeypots. These are starting points for investigation rather than standalone high-confidence detections, and should be correlated with authentication, key modification, and follow-on process activity.
| Signal | Where to look | What it indicates |
|---|---|---|
lockr binary on disk |
Filesystem - /tmp, /var/tmp, user home dirs |
Consistent with prior compromise staging; correlate with SSH key changes |
lockr -ia in command log |
Bash history, auditd, EDR process events | Stage 2 active; binary was or was not available |
passwd called via SSH session |
SSH session logs, PAM events | Post-exploitation credential rotation; competitive exclusion |
chattr -ia .ssh or equivalent |
Auditd, inotify on ~/.ssh |
SSH key manipulation imminent; may indicate Dota or similar worm |
The lockr binary name should be added to file integrity monitoring rules
and EDR watchlists. Its non-standard name makes it trivially distinguishable from legitimate
system utilities - any process creation or file write event for a binary named lockr
in a standard Linux environment is anomalous.
Methodology
Data was collected from four NullRoute nodes (DE, US, FR, SG) across a multi-month observation period. Node 1 (DE) runs T-Pot with authrandom authentication. Nodes 2-4 run standalone Cowrie with UserDB authentication (Node 3 was historically AuthRandom until 2026-04-03). The Failed-Intent Layer operates on 81,341 sessions in the current DuckDB analysis corpus.
We distinguish between two types of failure signals. Native Cowrie failures
are explicit cowrie.command.failed events where the emulated shell returned
command not found for a binary not in Cowrie's filesystem or command set.
Inferred failed-intent patterns are derived from session structure by the
analysis layer - for example, interactive aborts (where a scripted session encounters an
unexpected prompt) or fragmented shell constructs (where multi-line conditionals are sent
as individual commands). The lockr pattern is native; passwd_change
and shell_script are inferred.
The frustration score (failure-to-command ratio) is computed per session as the ratio of failed command events to total command events. Stage classification (Stage 1 vs Stage 2) is based on session length, command patterns, and Behavioral Genome family mapping - it is a derived classification, not a ground-truth label.
All analysis was performed on passive observation data only. No active interaction with attack infrastructure was conducted. IP addresses are withheld per NullRoute disclosure policy.
Related research: 98% of SSH Intrusions Come from One Worm · Two Bot Pools, Coordinated Timing · Behavioral Rigidity Spectrum · MDRFCKR Behavioral Dossier