Challenge / Forensics

PersistencelsFutile

PersistencelsFutile is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

MediumPublished 2024-07-06Sanitized local writeup

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.

PersistencelsFutile sanitized attack graph

Walkthrough flow

01

Treat the remote Linux host as an incident-response...

02

Run read-only persistence triage across SSH...

03

Group related artifacts into checker-facing issues...

04

Remove validated root SSH persistence, user crontab...

05

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.

100% coverage
Evidence verdict

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 ppppd binary.
  • Modified gnats account with root-group/login access and an active password hash.
  • Root shell startup bind shell through a fake alertd binary.
  • User shell startup reverse shell hidden behind a cat alias.
  • 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:

bash
cd <local workspace>
<secret redacted>='<challenge password>' python3 solve/solve.py --host <TARGET> --port 31754 --user user

The 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

FileSizeSHA256TypeNotes
0remote-only or no provided filesNo local artifacts found under files/

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-13T10:43:55Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-13T10:44:18Zartifact inventoryanalysis/artifact-inventory.json0 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-13T10:44:18Zhypothesis recordedhypothesis-board.mdPerform read-only Linux persistence triage over SSH, identify exactly eight remote-access or privilege-escalation backdoors, remove them, then run /root/solveme as root.MediumRun 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:18Zinstrumentation plananalysis/instrumentation-plan.mdIdentify and remove exactly eight persistence/backdoor mechanisms on the remote Linux server, then run /root/solveme as root.HighStop after two cleanup attempts that do not reduce /root/solveme findings; record the remaining suspected mechanisms before more mutation.
2026-06-13T10:44:34Zresearch taskanalysis/research/task-20260613T104434204756Z-6fcd7ea0.mdResearch task created for advisory investigationMediumRecord research output
2026-06-13T10:44:34Zcheckpoint recordedanalysis/checkpoint-analysis-20260613T104434206463Z-685f58f3.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-13T10:45:22ZRAG queryanalysis/rag/rag-query-20260613T104448281257Z-0d914fd5.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-13T10:45:40Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T10:45:49ZRAG recordanalysis/rag-records.mdRetrieved memory tagged PARTIALMediumValidate or reject with live evidence
2026-06-13T10:46:07Zevaluatoranalysis/evaluator-20260613T104607060394Z-6d2c904b.mdProceedHighRun read-only SSH inventory only; no mutation yet.
2026-06-13T10:50:54Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T10:50:54Zevaluatoranalysis/evaluator-20260613T105054597119Z-cc2a32a3.mdProceedHighGate before cleanup, execute cleanup remotely, run /root/solveme, capture result.
2026-06-13T10:52:58Zcleanup verificationanalysis/cleanup/cleanup-run-2.txtSeven checker issues were fully remediated; Issue 5 remained partial after removing the private reverse-shell payload.HighSearch for the residual Issue 5 launcher or trigger.
2026-06-13T10:53:40Zresidual analysisanalysis/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.HighRemove the MOTD launcher and rerun /root/solveme.
2026-06-13T10:54:08Zfinal cleanupanalysis/cleanup/cleanup-run-3.txtRemoved the MOTD launcher; /root/solveme reported Issues 1-8 fully remediated and printed the flag. Analysis copy is redacted; raw flag is in loot/.HighCapture flag through harness and complete writeup/lint.
2026-06-13T10:54:29Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-13T10:56:45Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional 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/private plus 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:

bash
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.

  1. Treat the remote Linux host as an incident-response cleanup target.
  2. Run read-only persistence triage across SSH configuration, cron, shell startup files, SUID/capability surfaces, users/groups, and running processes.
  3. Group related artifacts into checker-facing issues instead of counting every file separately.
  4. 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.
  5. When /root/solveme reports a partial remediation, search for launchers/triggers as well as payload files.
  6. 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.d is 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-check alone did not fully close Issue 5 because /etc/update-motd.d/30-connectivity-check still referenced it.
  • Process-table checks alone were insufficient after the payload disappeared; filesystem reference search found the remaining trigger.

Tool Quirks

  • strace was unavailable on the remote target, so /root/solveme behavior 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.txt
  • analysis/remote/suspect-deep-dive-1.txt
  • analysis/remote/suspect-deep-dive-2.txt
  • analysis/cleanup/backdoor-plan.md
  • analysis/cleanup/cleanup-run-1.txt
  • analysis/cleanup/cleanup-run-2.txt
  • analysis/cleanup/issue5-deep-dive-1.txt
  • analysis/cleanup/cleanup-run-3.txt
  • solve/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.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Perform 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.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit 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.