SEPC
SEPC is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
SEPC attack path
SEPC 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 Reversing evidence, validation, and reusable operator lessons.
Walkthrough flow
Extract the initramfs and inspect /init.
Identify the challenge-specific files: userland...
Use the init script to confirm the module is loaded...
Reverse the userland checker enough to confirm it...
Reverse checker.ko; the read handler validates the...
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.
- Reversing/SEPC/writeup.md
- htb-challenge/Reversing/SEPC/notes.md
- htb-challenge/Reversing/SEPC/memory-summary.md
- htb-challenge/Reversing/SEPC/hypothesis-board.md
Technical Walkthrough
Writeup
Challenge
- Name: SEPC
- Category: Reversing
- Difficulty: Medium
- Mode: file
Summary
SEPC ships a small Linux initramfs with a userland checker and a kernel module. The userland binary is mostly a UI wrapper; the actual validation happens in checker.ko, exposed through /dev/checker.
The kernel module validates the entered key one byte at a time. Each expected byte is the XOR of two byte arrays stored in the module's .rodata. Reconstructing those bytes gives the accepted HTB flag prefix; the solver appends the required closing brace and captures the flag through the harness.
Artifact Inventory
files/extracted/rev_sepc/run.sh: QEMU launcher.files/extracted/rev_sepc/bzImage: Linux kernel image.files/extracted/rev_sepc/initramfs.cpio.gz: embedded filesystem.analysis/initramfs/init: boot script that loadschecker.ko, creates/dev/checker, and runs/checker.analysis/initramfs/checker: stripped static x86-64 userland checker.analysis/initramfs/checker.ko: not-stripped x86-64 kernel module containing the key validation logic.
Analysis
The init script in analysis/init-script.txt shows the important flow: load the module, create /dev/checker, then execute /checker.
Static analysis of checker showed that it prompts for a security key, opens /dev/checker, writes each input byte, reads one status byte back, and prints rejected on 0, continues on 1, and prints verified on 2. The relevant loop is documented in analysis/source-audit.md and visible in analysis/checker-disasm.txt.
The module disassembly in analysis/checker-ko-disasm.txt shows the validation formula:
expected[i] = rodata[0x60 + i] XOR rodata[0x20 + i]The module returns status 2 after 0x22 correct bytes. analysis/module-xor-derivation.txt records a sanitized derivation summary. Since the recovered bytes form an HTB-style prefix but the kernel stops before the closing brace, the solver appends the required terminator to produce the final flag format.
Solve
The reproducible solver is:
python3 Reversing/SEPC/solve/solve.py \
--module Reversing/SEPC/analysis/initramfs/checker.ko \
--output Reversing/SEPC/loot/flag-candidate.txt \
--summary Reversing/SEPC/analysis/solve-summary.jsonIt parses the ELF section table, extracts .rodata, XORs the two validation arrays, appends the closing brace when needed, and writes the candidate to loot/flag-candidate.txt.
The challenge was completed by running the solver through scripts/challenge_exec.py, then capturing the flag with scripts/challenge_harness.py capture-flag.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- In embedded reversing challenges, the initramfs boot script often identifies the small custom attack surface faster than broad kernel-image analysis.
- A userland checker may only orchestrate I/O; the real secret check can live in a kernel module, device driver, or ioctl/read/write handler.
- Not-stripped kernel modules are high-value: symbols, relocations, and
.rodataoffsets can be enough without a full decompiler. - When a checker accepts a prefix and stops early, normalize the recovered value to the platform flag format before capture.
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: SEPC
- Category: Reversing
- Difficulty: Medium
- Mode: file
- Remote instance: none
- Start time: 2026-06-12T23:44:19Z
- 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 |
|---|---|---|---|---|
files/a12c7384-284d-4eb9-949a-d6f9207d91a1.zip | 9631097 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 4 shown in artifact inventory JSON |
files/extracted/rev_sepc/bzImage | 8854208 | <hash redacted> | Linux kernel x86 boot executable bzImage, version 6.4.0-rc7-00002-gdbad9ce9397e (user@build) #6 SMP <secret redacted> Mon Jun 19 21:09:32 BST 2023, RO-rootFS, swap_dev 0X8, Normal VGA | |
files/extracted/rev_sepc/initramfs.cpio.gz | 910621 | <hash redacted> | gzip compressed data, from Unix, original size modulo 2^32 1573416 | |
files/extracted/rev_sepc/run.sh | 132 | <hash redacted> | POSIX shell script text executable, ASCII text |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T23:44:19Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T23:44:19Z | artifact inventory | analysis/artifact-inventory.json | 4 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T23:47:46Z | hypothesis recorded | hypothesis-board.md | Recover the accepted security key by reversing checker.ko char-device validation: XOR rodata[0x20+i] with rodata[0x60+i], then append the HTB closing brace. | High | Run solve/solve.py to parse .rodata from checker.ko, write loot/flag-candidate.txt, then capture via harness. |
| 2026-06-12T23:47:46Z | research skip | analysis/research/research-skip.md | Research intentionally skipped with recorded reason | Medium | Gate before exploit |
| 2026-06-12T23:47:46Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T23:47:46Z | instrumentation plan | analysis/instrumentation-plan.md | Reproduce the kernel-module byte validation algorithm and recover the accepted security key as an HTB flag. | High | Stop if the solver output is not HTB-formatted, the .rodata section cannot be parsed, or the harness capture rejects the candidate. |
| 2026-06-12T23:47:46Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T234746254258Z-cc3d10e8.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T23:47:56Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T23:48:15Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T23:50:32Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
-
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: Reversing
- Challenge: SEPC
- 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.
- Extract the initramfs and inspect
/init. - Identify the challenge-specific files: userland
checkerand kernel modulechecker.ko. - Use the init script to confirm the module is loaded and exposed as
/dev/checker. - Reverse the userland checker enough to confirm it writes one input byte to
/dev/checkerand reads a one-byte status. - Reverse
checker.ko; the read handler validates the last byte written against two arrays in.rodata. - Recover the accepted key prefix with
rodata[0x60+i] XOR rodata[0x20+i]for0x22bytes. - Normalize the recovered prefix into a full HTB-formatted flag and capture it through the harness.
Reusable Lessons
- For initramfs reversing, inspect
/initfirst to identify custom binaries/modules. - Not-stripped kernel modules can be solved effectively with
nm,objdump -d -r, and.rodataextraction. - Char-device challenges often split write/read state: write stores a byte, read validates and returns a status.
- If a checker returns success before consuming the whole input, the accepted sequence may be a prefix that still needs flag-format normalization.
Dead Ends
- The full
bzImagedid not need deep analysis; the custom logic was in the initramfs. - QEMU execution was not required locally once the module algorithm was reconstructed statically.
- Public/RAG research was skipped because the local artifacts directly exposed the validation logic.
Tool Quirks
qemu-system-x86_64was not installed locally.binwalk,radare2,rizin, and Ghidra were missing, but core tools were enough.- macOS
objdump,strings,cpio, and Python ELF section parsing were sufficient.
Evidence Paths
analysis/init-script.txtanalysis/checker-disasm.txtanalysis/checker-ko-disasm.txtanalysis/checker-ko-data.txtanalysis/module-xor-derivation.txtanalysis/source-audit.mdsolve/solve.pyanalysis/solve-summary.jsonloot/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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Recover the accepted security key by reversing checker.ko char-device validation: XOR rodata[0x20+i] with rodata[0x60+i], then append the HTB closing brace. | init loads checker.ko and runs checker; userland writes bytes to /dev/checker and reads status; module disassembly shows expected byte is XOR of two .rodata arrays and returns status 2 after 34 correct bytes. | Need reproducible extraction from checker.ko and harness flag validation. | Run solve/solve.py to parse .rodata from checker.ko, write loot/flag-candidate.txt, then capture via harness. | High | active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|
Technical analogy
How to remember this solve
Think of it like taking apart a small appliance on a workbench. You do not need every screw at once; you trace the control path and rebuild just enough logic to make it reveal the answer.
For SEPC, 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.