PersistencelsFutile
PersistencelsFutile is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
PersistencelsFutile attack path
PersistencelsFutile is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Objective
Challenge walkthrough focused on Forensics evidence, validation, and reusable operator lessons.
Walkthrough flow
Treat the remote Linux host as an incident-response...
Run read-only persistence triage across SSH...
Group related artifacts into checker-facing issues...
Remove validated root SSH persistence, user crontab...
When /root/solveme reports a partial remediation,...
Source coverage
High source coverage
Status: complete. This article is generated from 4 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.
High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.
- Forensics/PersistencelsFutile/writeup.md
- htb-challenge/Forensics/PersistencelsFutile/notes.md
- htb-challenge/Forensics/PersistencelsFutile/memory-summary.md
- htb-challenge/Forensics/PersistencelsFutile/hypothesis-board.md
Technical Walkthrough
Writeup
Challenge
- Name: PersistencelsFutile
- Category: Forensics
- Difficulty: Medium
- Mode: remote
Summary
PersistencelsFutile was a remote Linux incident-response challenge. The goal was to identify and remove eight persistence/backdoor issues on a production-like server, then run /root/solveme as root.
The solve was evidence-driven: collect a sudo-backed persistence inventory, group related artifacts into checker issues, remove only validated malicious files/config lines, and use /root/solveme as the final source of truth. The last partial issue was caused by a leftover MOTD launcher after the reverse-shell payload itself had already been deleted.
Artifact Inventory
This was a remote-only challenge with no provided local archive. The initial artifact inventory in analysis/artifact-inventory.json records the empty files/ set and the live SSH target.
Analysis
The read-only inventory in analysis/remote/read-only-inventory-1.txt and deeper triage in analysis/remote/suspect-deep-dive-1.txt / analysis/remote/suspect-deep-dive-2.txt exposed the persistence set:
- Root SSH persistence: extra root authorized key, a Python helper that restored it, a cron runner for that helper, and permissive
PermitRootLogin. - User crontab command execution through a DNS TXT lookup.
- Cron-based SUID shell dropper.
- Existing SUID shell copies in user and system paths.
- Fake SUID
ppppdbinary. - Modified
gnatsaccount with root-group/login access and an active password hash. - Root shell startup bind shell through a fake
alertdbinary. - User shell startup reverse shell hidden behind a
catalias. - Private reverse-shell loop under
/var/lib/private/connectivity-check.
The cleanup plan in analysis/cleanup/backdoor-plan.md grouped those artifacts into the checker-facing issues. analysis/cleanup/cleanup-run-1.txt remediated most items but failed to remove the private reverse-shell loop because of a shell expansion bug in the process-kill loop. analysis/cleanup/cleanup-run-2.txt removed the payload file and killed matching processes, leaving Issue 5 only partially remediated.
The decisive follow-up was analysis/cleanup/issue5-deep-dive-1.txt, which found /etc/update-motd.d/30-connectivity-check still launching /var/lib/private/connectivity-check on login. Removing that launcher in analysis/cleanup/cleanup-run-3.txt made /root/solveme report Issues 1-8 fully remediated.
Solve
The reproducible cleanup script is solve/solve.py. It accepts host, port, and user arguments and reads the SSH/sudo password from <secret redacted> or an interactive prompt. It removes only the validated malicious artifacts and then runs /root/solveme.
Example:
cd <local workspace>
<secret redacted>='<challenge password>' python3 solve/solve.py --host <TARGET> --port 31754 --user userThe final successful verifier output is preserved in analysis/cleanup/cleanup-run-3.txt, with the raw flag redacted from analysis.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- When a checker says an issue is partially remediated, search for both payload and launcher. Here, the reverse-shell script was gone, but the MOTD startup trigger remained.
- Group related artifacts by persistence mechanism rather than counting files one by one.
- Redact raw flags and private material from analysis outputs after capture; keep raw values in
loot/only.
Source-Backed Dossier
The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.
Notes
Scope
- Challenge: PersistencelsFutile
- Category: Forensics
- Difficulty: Medium
- Mode: remote
- Remote instance: <TARGET>:31754
- Start time: 2026-06-13T10:43:55Z
- Operator: harness
- State file:
challenge-state.json
Harness Status
- Current phase: see
challenge-state.json - Next allowed actions: see
next-action.json - Raw flags and sensitive material stay in
loot/only. Do not paste them here.
Artifact Inventory
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
| — | 0 | — | remote-only or no provided files | No local artifacts found under files/ |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-13T10:43:55Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-13T10:44:18Z | artifact inventory | analysis/artifact-inventory.json | 0 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-13T10:44:18Z | hypothesis recorded | hypothesis-board.md | Perform read-only Linux persistence triage over SSH, identify exactly eight remote-access or privilege-escalation backdoors, remove them, then run /root/solveme as root. | Medium | Run sudo-backed read-only inventory of users, sudoers, services/timers, cron, shell startup, SSH authorized_keys, SUID/capabilities, writable paths, and network listeners; do not mutate until a cleanup list is evidence-backed. |
| 2026-06-13T10:44:18Z | instrumentation plan | analysis/instrumentation-plan.md | Identify and remove exactly eight persistence/backdoor mechanisms on the remote Linux server, then run /root/solveme as root. | High | Stop after two cleanup attempts that do not reduce /root/solveme findings; record the remaining suspected mechanisms before more mutation. |
| 2026-06-13T10:44:34Z | research task | analysis/research/task-20260613T104434204756Z-6fcd7ea0.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-13T10:44:34Z | checkpoint recorded | analysis/checkpoint-analysis-20260613T104434206463Z-685f58f3.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-13T10:45:22Z | RAG query | analysis/rag/rag-query-20260613T104448281257Z-0d914fd5.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-13T10:45:40Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-13T10:45:49Z | RAG record | analysis/rag-records.md | Retrieved memory tagged PARTIAL | Medium | Validate or reject with live evidence |
| 2026-06-13T10:46:07Z | evaluator | analysis/evaluator-20260613T104607060394Z-6d2c904b.md | Proceed | High | Run read-only SSH inventory only; no mutation yet. |
| 2026-06-13T10:50:54Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-13T10:50:54Z | evaluator | analysis/evaluator-20260613T105054597119Z-cc2a32a3.md | Proceed | High | Gate before cleanup, execute cleanup remotely, run /root/solveme, capture result. |
| 2026-06-13T10:52:58Z | cleanup verification | analysis/cleanup/cleanup-run-2.txt | Seven checker issues were fully remediated; Issue 5 remained partial after removing the private reverse-shell payload. | High | Search for the residual Issue 5 launcher or trigger. |
| 2026-06-13T10:53:40Z | residual analysis | analysis/cleanup/issue5-deep-dive-1.txt | /etc/update-motd.d/30-connectivity-check still launched /var/lib/private/connectivity-check, explaining the partial remediation state. | High | Remove the MOTD launcher and rerun /root/solveme. |
| 2026-06-13T10:54:08Z | final cleanup | analysis/cleanup/cleanup-run-3.txt | Removed the MOTD launcher; /root/solveme reported Issues 1-8 fully remediated and printed the flag. Analysis copy is redacted; raw flag is in loot/. | High | Capture flag through harness and complete writeup/lint. |
| 2026-06-13T10:54:29Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-13T10:56:45Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- The challenge required removing eight checker issues, but several issues contained multiple related artifacts.
- Issue 1 grouped root SSH persistence: an extra authorized key, a key-restoring Python helper, a cron runner, and permissive root SSH login.
- Issue 5 grouped a reverse-shell payload under
/var/lib/privateplus the login-triggered MOTD launcher under/etc/update-motd.d/. - The final blocker was not a running process; it was the stale MOTD launcher that survived after the payload file was removed.
RAG / Advisory Memory
RAG output is advisory only. Record evaluated retrievals with:
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."Secrets/Flags
Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.
Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Forensics
- Challenge: PersistencelsFutile
- Difficulty: Medium
- Source workspace:
<local workspace>
Validated Solve Chain
Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.
- Treat the remote Linux host as an incident-response cleanup target.
- Run read-only persistence triage across SSH configuration, cron, shell startup files, SUID/capability surfaces, users/groups, and running processes.
- Group related artifacts into checker-facing issues instead of counting every file separately.
- Remove validated root SSH persistence, user crontab command execution, cron SUID droppers, SUID shell copies, fake SUID service binaries, account tampering, shell startup bind/reverse shells, and private reverse-shell loop artifacts.
- When
/root/solvemereports a partial remediation, search for launchers/triggers as well as payload files. - Final missing trigger was a MOTD script that launched a now-removed private reverse-shell payload.
Reusable Lessons
- Linux persistence cleanup challenges often combine payloads, launchers, and configuration weakening into one checker issue.
update-motd.dis a valid login-triggered persistence location and should be included in shell/login persistence triage.- A removed payload can still leave a challenge issue partially remediated if a startup trigger remains.
- Keep raw checker flags in
loot/only and redact analysis copies after capture.
Dead Ends
- Killing/removing
/var/lib/private/connectivity-checkalone did not fully close Issue 5 because/etc/update-motd.d/30-connectivity-checkstill referenced it. - Process-table checks alone were insufficient after the payload disappeared; filesystem reference search found the remaining trigger.
Tool Quirks
stracewas unavailable on the remote target, so/root/solvemebehavior had to be inferred from status output and validated live cleanup.- A shell expansion mistake in the first process-kill loop left the private reverse-shell payload in place; later cleanup used narrower process and file checks.
Evidence Paths
analysis/remote/read-only-inventory-1.txtanalysis/remote/suspect-deep-dive-1.txtanalysis/remote/suspect-deep-dive-2.txtanalysis/cleanup/backdoor-plan.mdanalysis/cleanup/cleanup-run-1.txtanalysis/cleanup/cleanup-run-2.txtanalysis/cleanup/issue5-deep-dive-1.txtanalysis/cleanup/cleanup-run-3.txtsolve/solve.py
Ingestion Decision
- Proposed for LightRAG: yes
- Requires user approval before ingestion: yes
Hypothesis Board
Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Perform read-only Linux persistence triage over SSH, identify exactly eight remote-access or privilege-escalation backdoors, remove them, then run /root/solveme as root. | Scenario states IR found eight backdoors and the user has SSH plus sudo rights to the isolated server. | Run sudo-backed read-only inventory of users, sudoers, services/timers, cron, shell startup, SSH authorized_keys, SUID/capabilities, writable paths, and network listeners; do not mutate until a cleanup list is evidence-backed. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|
Technical analogy
How to remember this solve
Think of the challenge as a small system with one rule that matters more than the rest. The solve is finding that rule, validating it, and using it carefully enough to reach the final proof.
For PersistencelsFutile, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.