Challenge / Reversing

SEPC

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

MediumPublished 2025-07-23Sanitized local writeup

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.

SEPC sanitized attack graph

Walkthrough flow

01

Extract the initramfs and inspect /init.

02

Identify the challenge-specific files: userland...

03

Use the init script to confirm the module is loaded...

04

Reverse the userland checker enough to confirm it...

05

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.

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.

  • 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 loads checker.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:

text
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:

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

It 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 .rodata offsets 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

FileSizeSHA256TypeNotes
files/a12c7384-284d-4eb9-949a-d6f9207d91a1.zip9631097<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 4 shown in artifact inventory JSON
files/extracted/rev_sepc/bzImage8854208<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.gz910621<hash redacted>gzip compressed data, from Unix, original size modulo 2^32 1573416
files/extracted/rev_sepc/run.sh132<hash redacted>POSIX shell script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T23:44:19Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T23:44:19Zartifact inventoryanalysis/artifact-inventory.json4 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T23:47:46Zhypothesis recordedhypothesis-board.mdRecover 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.HighRun solve/solve.py to parse .rodata from checker.ko, write loot/flag-candidate.txt, then capture via harness.
2026-06-12T23:47:46Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-12T23:47:46Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T23:47:46Zinstrumentation plananalysis/instrumentation-plan.mdReproduce the kernel-module byte validation algorithm and recover the accepted security key as an HTB flag.HighStop 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:46Zcheckpoint recordedanalysis/checkpoint-analysis-20260612T234746254258Z-cc3d10e8.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-12T23:47:56Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T23:48:15Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T23:50:32Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

-

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

  1. Extract the initramfs and inspect /init.
  2. Identify the challenge-specific files: userland checker and kernel module checker.ko.
  3. Use the init script to confirm the module is loaded and exposed as /dev/checker.
  4. Reverse the userland checker enough to confirm it writes one input byte to /dev/checker and reads a one-byte status.
  5. Reverse checker.ko; the read handler validates the last byte written against two arrays in .rodata.
  6. Recover the accepted key prefix with rodata[0x60+i] XOR rodata[0x20+i] for 0x22 bytes.
  7. Normalize the recovered prefix into a full HTB-formatted flag and capture it through the harness.

Reusable Lessons

  • For initramfs reversing, inspect /init first to identify custom binaries/modules.
  • Not-stripped kernel modules can be solved effectively with nm, objdump -d -r, and .rodata extraction.
  • 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 bzImage did 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_64 was 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.txt
  • analysis/checker-disasm.txt
  • analysis/checker-ko-disasm.txt
  • analysis/checker-ko-data.txt
  • analysis/module-xor-derivation.txt
  • analysis/source-audit.md
  • solve/solve.py
  • analysis/solve-summary.json
  • 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
1Recover 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.Highactive

Closed Branches

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