INV-004 April 2026

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.

81,341 Sessions Analyzed
2,457 Sessions with Failures
4 Failure Patterns Identified

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.

lockr (tool invocation) 2,277
passwd_change (interactive abort) 1,312
shell_script (interactive abort) 55
uptime_parse (environment probe) 42

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.

Node 2 (US) - Healthcare Persona
46.4%
Dota frustration score
Top failure: shell_script
Sessions with failures: 14
Typical session length: 3 commands
Stage reached: Stage 1 (abort)
Node 3 (FR) - AI/ML Persona
7.8%
Dota frustration score
Top failure: lockr
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