Challenge / ICS

Factory

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

EasyPublished 2024-11-16Sanitized local writeup

Scenario

Factory attack path

Factory 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 ICS evidence, validation, and reusable operator lessons.

Factory sanitized attack graph

Walkthrough flow

01

Extract the challenge archive and inspect the PLC...

02

Identify the PLC Modbus slave address from the setup...

03

Extract the documented coil map for manual-mode...

04

Read the ladder logic to determine the intended...

05

Probe the remote menu and status endpoint to...

Source coverage

High source coverage

Status: complete. This article is generated from 6 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.

  • ICS/Factory/writeup.md
  • htb-challenge/ICS/Factory/notes.md
  • htb-challenge/ICS/Factory/memory-summary.md
  • htb-challenge/ICS/Factory/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__ICS__Factory__memory-summary.md.efb8cc30c2.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__ICS__Factory__notes.md.7f48b7a817.md

Technical Walkthrough

Writeup

Challenge

  • Name: Factory
  • Category: ICS
  • Difficulty: Easy
  • Mode: hybrid

Summary

Factory provides a PLC ladder-logic printout and a diagram for a remote Modbus bridge. The target system starts in automatic mode with the inlet valve open and the outlet valve closed. The artifact documents the PLC slave address and key control coils. By switching the PLC into manual mode, stopping the inlet, and forcing the outlet valve, the system reaches the desired safe state and the remote status endpoint returns the flag.

Artifact Inventory

Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.

  • files/a12c7392-447e-40ef-9afd-3572c30cd698.zip: original HTB archive.
  • analysis/extracted/PLC_Ladder_Logic.pdf: one-page ladder logic for water_storage_facility.
  • analysis/extracted/interface_setup.png: remote Modbus bridge diagram with PLC slave address and coil map.
  • analysis/menu-probes.txt: remote menu/status probing.
  • analysis/modbus-read-probes.txt: accepted read-only Modbus frame tests.
  • analysis/solve-transcript.txt: sanitized transcript from the successful run.
  • solve/solve.py: reproducible solver.

Analysis

  1. interface_setup.png identifies PLC-1 as Modbus slave address 82 decimal, or 0x52.
  2. The same diagram lists the relevant coils:

- manual_mode_control at decimal 9947

- cutoff_in at decimal 26

- force_start_out at decimal 52

- cutoff at decimal 5

- force_start_in at decimal 1336

  1. The ladder-logic PDF shows automatic/manual mode selection. When manual_mode_control is set, auto_mode drops and manual_mode becomes active.
  2. In manual mode, cutoff_in drives stop_in, which stops the inlet branch.
  3. In manual mode, force_start_out can drive the outlet valve branch as long as stop_out is not asserted.
  4. The remote menu exposes status and Modbus command forwarding. Initial status showed automatic mode active, inlet open, outlet closed, and an empty flag placeholder.
  5. Read-only frames such as 52 01 00 0c 00 01 were accepted without CRC, confirming the bridge expects a hex-encoded Modbus RTU ADU without the CRC bytes.
  6. The solve sequence writes the minimum controls with function 0x05 single-coil writes:

- clear cutoff

- clear force_start_in

- set manual_mode_control

- set cutoff_in

- set force_start_out

  1. Final status showed manual mode active, inlet closed, outlet open, and a returned HTB-format flag.

Solve

Run:

bash
python3 ICS/Factory/solve/solve.py --host <TARGET> --port 31344

The solver connects to the menu service, sends documented Modbus single-coil writes without CRC, requests final system status, and prints the returned flag for harness capture.

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • In ICS challenges, diagrams and ladder-logic labels are often the primary exploit documentation.
  • Confirm the transport framing with read-only commands before writing coils.
  • For PLC logic puzzles, target the control conditions that the ladder already exposes instead of trying to directly overwrite output coils that may be recalculated every scan.

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: Factory
  • Category: ICS
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T10:46:11Z
  • 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
files/a12c7392-447e-40ef-9afd-3572c30cd698.zip193358<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T10:46:11Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T10:46:27Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T10:47:47Zhypothesis recordedhypothesis-board.mdUse Modbus coil writes against slave 82 to force manual mode, stop inlet, and start outletMediumConnect to <TARGET>:31344, determine accepted Modbus command encoding, then perform minimal read/write probes on documented coil addresses
2026-06-10T10:49:02Zresearch recordanalysis/research/research-records.mdResearch tagged MISSINGMediumValidate against current evidence
2026-06-10T10:51:18Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T105118425429Z-94a0eafe.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T10:52:27Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T10:52:53Zevaluatoranalysis/evaluator-20260610T105253208217Z-4d44afd5.mdProceedHighRun gate before exploit, then execute solve/controlled coil write sequence.
2026-06-10T10:55:35Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T10:57:21Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Artifact extraction produced PLC_Ladder_Logic.pdf and interface_setup.png.
  • The interface setup shows PLC-1 at Modbus slave address 82 decimal (0x52) and lists coil addresses for in_valve, out_valve, start, manual_mode_control, cutoff, cutoff_in, force_start_out, and force_start_in.
  • The ladder logic shows manual mode can be selected via manual_mode_control; in manual mode cutoff_in drives stop_in, and force_start_out can drive out_valve.
  • The remote instance at <TARGET>:31344 is a menu interface with Get status of system and Send modbus command.
  • Initial live status shows auto_mode=1, manual_mode=0, in_valve=1, out_valve=0, and an empty placeholder flag.
  • Read-only Modbus frames in the form slave function address count without CRC are accepted by the remote bridge and did not alter the system state.
  • Current intended branch: use function 0x05 single-coil writes against documented coils to set manual mode, stop inlet flow, and force the outlet valve.
  • solve/solve.py executed the controlled coil sequence and final status showed manual mode active, inlet closed, outlet open, and an HTB-format flag returned.
  • The harness captured the raw flag into loot/flag.txt; non-loot transcripts are sanitized.

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: ICS
  • Challenge: Factory
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extract the challenge archive and inspect the PLC ladder-logic PDF plus remote-interface diagram.
  2. Identify the PLC Modbus slave address from the setup diagram.
  3. Extract the documented coil map for manual-mode selection, inlet stop, and outlet force controls.
  4. Read the ladder logic to determine the intended safe-state controls instead of guessing raw output writes.
  5. Probe the remote menu and status endpoint to establish baseline state.
  6. Send read-only Modbus frames to validate that the bridge accepts hex-encoded Modbus RTU ADU bytes without CRC.
  7. Use function 0x05 single-coil writes to enter manual mode, stop the inlet branch, and force the outlet branch.
  8. Request final status and capture the returned flag through the harness.

Reusable Lessons

  • For Modbus bridge challenges, test read-only frames first to learn whether CRC bytes are expected.
  • Ladder logic can reveal safer intermediate control coils that survive PLC scan recalculation better than direct output-coil writes.
  • Manual-mode interlocks are often the intended way to override broken sensor logic in an ICS puzzle.
  • Keep raw status responses with flags out of analysis/; redact them and store the raw flag only under loot/.

Dead Ends

  • RAG/private memory had no matching Factory solve pattern, so the solve relied on current artifact and live-service evidence.
  • Direct source of the flag was not in the PDF/image; it required reaching the correct PLC state through the remote service.

Tool Quirks

  • The remote service is a menu wrapper around a Modbus command forwarder, not a raw Modbus TCP endpoint.
  • The command prompt accepts no-CRC hex frames such as slave function address value.
  • The status JSON contains a flag string with braces; parsers should slice from the first { to the last } rather than using a non-greedy brace regex.

Evidence Paths

  • analysis/extracted/PLC_Ladder_Logic.pdf
  • analysis/extracted/interface_setup.png
  • analysis/rendered/plc_ladder-1.png
  • analysis/menu-probes.txt
  • analysis/modbus-read-probes.txt
  • analysis/solve-transcript.txt
  • solve/solve.py
  • loot/flag.txt

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
1Use Modbus coil writes against slave 82 to force manual mode, stop inlet, and start outletinterface_setup.png lists PLC slave address 82 and coil addresses for manual_mode_control, cutoff_in, force_start_out; PLC_Ladder_Logic.pdf shows manual controls drive stop_in and out_valveConnect to <TARGET>:31344, determine accepted Modbus command encoding, then perform minimal read/write probes on documented coil addressesMediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: ICS
  • Challenge: Factory
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extract the challenge archive and inspect the PLC ladder-logic PDF plus remote-interface diagram.
  2. Identify the PLC Modbus slave address from the setup diagram.
  3. Extract the documented coil map for manual-mode selection, inlet stop, and outlet force controls.
  4. Read the ladder logic to determine the intended safe-state controls instead of guessing raw output writes.
  5. Probe the remote menu and status endpoint to establish baseline state.
  6. Send read-only Modbus frames to validate that the bridge accepts hex-encoded Modbus RTU ADU bytes without CRC.
  7. Use function 0x05 single-coil writes to enter manual mode, stop the inlet branch, and force the outlet branch.
  8. Request final status and capture the returned flag through the harness.

Reusable Lessons

  • For Modbus bridge challenges, test read-only frames first to learn whether CRC bytes are expected.
  • Ladder logic can reveal safer intermediate control coils that survive PLC scan recalculation better than direct output-coil writes.
  • Manual-mode interlocks are often the intended way to override broken sensor logic in an ICS puzzle.
  • Keep raw status responses with flags out of analysis/; redact them and store the raw flag only under loot/.

Dead Ends

  • RAG/private memory had no matching Factory solve pattern, so the solve relied on current artifact and live-service evidence.
  • Direct source of the flag was not in the PDF/image; it required reaching the correct PLC state through the remote service.

Tool Quirks

  • The remote service is a menu wrapper around a Modbus command forwarder, not a raw Modbus TCP endpoint.
  • The command prompt accepts no-CRC hex frames such as slave function address value.
  • The status JSON contains a flag string with braces; parsers should slice from the first { to the last } rather than using a non-greedy brace regex.

Evidence Paths

  • analysis/extracted/PLC_Ladder_Logic.pdf
  • analysis/extracted/interface_setup.png
  • analysis/rendered/plc_ladder-1.png
  • analysis/menu-probes.txt
  • analysis/modbus-read-probes.txt
  • analysis/solve-transcript.txt
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: Factory
  • Category: ICS
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T10:46:11Z
  • 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
files/a12c7392-447e-40ef-9afd-3572c30cd698.zip193358<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T10:46:11Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T10:46:27Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T10:47:47Zhypothesis recordedhypothesis-board.mdUse Modbus coil writes against slave 82 to force manual mode, stop inlet, and start outletMediumConnect to <TARGET>:31344, determine accepted Modbus command encoding, then perform minimal read/write probes on documented coil addresses
2026-06-10T10:49:02Zresearch recordanalysis/research/research-records.mdResearch tagged MISSINGMediumValidate against current evidence
2026-06-10T10:51:18Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T105118425429Z-94a0eafe.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T10:52:27Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T10:52:53Zevaluatoranalysis/evaluator-20260610T105253208217Z-4d44afd5.mdProceedHighRun gate before exploit, then execute solve/controlled coil write sequence.
2026-06-10T10: <REDACTED>
2026-06-10T10:57:21Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Artifact extraction produced PLC_Ladder_Logic.pdf and interface_setup.png.
  • The interface setup shows PLC-1 at Modbus slave address 82 decimal (0x52) and lists coil addresses for in_valve, out_valve, start, manual_mode_control, cutoff, cutoff_in, force_start_out, and force_start_in.
  • The ladder logic shows manual mode can be selected via manual_mode_control; in manual mode cutoff_in drives stop_in, and force_start_out can drive out_valve.
  • The remote instance at <TARGET>:31344 is a menu interface with Get status of system and Send modbus command.
  • Initial live status shows auto_mode=1, manual_mode=0, in_valve=1, out_valve=0, and an empty placeholder flag.
  • Read-only Modbus frames in the form slave function address count without CRC are accepted by the remote bridge and did not alter the system state.
  • Current intended branch: use function 0x05 single-coil writes against documented coils to set manual mode, stop inlet flow, and force the outlet valve.
  • solve/solve.py executed the controlled coil sequence and final status showed manual mode active, inlet closed, outlet open, and an HTB-format flag returned.
  • The harness captured the raw flag into loot/flag.txt; non-loot transcripts are sanitized.

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.

Technical analogy

How to remember this solve

Think of the industrial system like a control-room checklist. You map the inputs, outputs, and assumptions, then find the one control path that accepts a state it should have rejected.

For Factory, 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.