Challenge / Reversing

Wayback

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

MediumPublished 2025-08-12Sanitized local writeup

Scenario

Wayback attack path

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

Wayback sanitized attack graph

Walkthrough flow

01

Reverse the Linux C++ key generator to recover its...

02

Identify the seed as calendar time YYYYMMDDhhmmss...

03

Reproduce glibc rand() in a Linux environment and...

04

Search the scenario's two-day calendar window for...

05

Use each candidate as the null-padded key for 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/Wayback/writeup.md
  • htb-challenge/Reversing/Wayback/notes.md
  • htb-challenge/Reversing/Wayback/memory-summary.md
  • htb-challenge/Reversing/Wayback/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Wayback
  • Category: Reversing
  • Difficulty: Medium
  • Mode: file

Summary

Wayback ships a Linux key generator (V1) and a Python AES-CBC decryptor. The binary uses the local calendar time to seed glibc rand(), then generates a candidate key string from a configurable character set. The challenge description narrows the original generation time to 2013-12-10 through 2013-12-11 and states the candidate length and character classes. Replaying the exact generator over that two-day window recovered the AES key and decrypted the flag.

Artifact Inventory

  • files/a12c733b-711e-4898-ad0c-e31539201d1f.zip: original HTB archive.
  • files/extracted/rev_wayback/V1: Linux x86-64 C++ ELF key generator.
  • files/extracted/rev_wayback/decrypt.py: AES-CBC decryptor with embedded ciphertext.
  • analysis/artifact-inventory.json: hashes, sizes, and archive listing.

Analysis

The useful evidence came from local static analysis, not from public or RAG sources. The RAG result was recorded as generic advisory context in analysis/rag-records.md; the local prior note was only used as a reminder that glibc rand() must be reproduced exactly.

The binary audit is recorded in analysis/source-audit.md. The relevant disassembly is in analysis/local/V1-main-disasm.txt, and the string data is in analysis/local/V1.rodata.txt.

generate_password[abi:cxx11](int, bool, bool) builds the character set as:

  • lowercase ASCII letters
  • uppercase ASCII letters
  • symbols !@#$%^&*_+ if enabled
  • digits 0123456789 if enabled

The challenge text says the original key string is 20 characters long and includes alphanumeric characters and symbols, so the solve uses all four character groups.

The generator calls time(NULL), converts it with localtime, and builds a decimal calendar seed equivalent to YYYYMMDDhhmmss modulo 2^32. It then calls srand(seed) and picks each character with rand() % len(charset).

decrypt.py pads the candidate password to a 32-byte AES key with null bytes, treats the first 16 encrypted bytes as the IV, decrypts the remaining ciphertext with AES-CBC, and checks PKCS#7 padding.

Because V1 is a Linux/glibc binary, the solver does not use macOS rand() or Python random. It uses a Linux Docker Python process and ctypes.CDLL("libc.so.6") to call glibc srand and rand. The reconstruction was validated against the real binary in analysis/local/generator-validation.txt.

Solve

The reproducible solver is solve/solve.py.

Validation command:

bash
cd <local workspace>
/opt/homebrew/bin/python3 solve/solve.py --validate-current-binary

Solve command:

bash
cd <local workspace>
/opt/homebrew/bin/python3 solve/solve.py

The solver searched the inclusive calendar window from 2013-12-10 00:00:00 through 2013-12-11 23:59:59. It found the decrypting candidate at calendar second 2013-12-11T13:01:25, with seed 699413773, after 133286 candidates. The non-sensitive summary is stored in analysis/solution-summary.json. The recovered key string is stored in loot/<password redacted>.

Flag

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

Lessons

  • For reversing challenges that use C rand(), reproduce the target libc implementation. macOS and Python PRNG behavior are not interchangeable with glibc.
  • Calendar-derived seeds can be much smaller than the apparent password space when the date range is known.
  • Validate keygen reconstruction against the original binary before trusting a brute-force search result.
  • Keep raw recovered <password redacted> and flags in loot/; use analysis/solution-summary.json for publishable metadata.

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: Wayback
  • Category: Reversing
  • Difficulty: Medium
  • Mode: file
  • Remote instance: none
  • Start time: 2026-06-12T07:34:03Z
  • 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/a12c733b-711e-4898-ad0c-e31539201d1f.zip5456<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 3 shown in artifact inventory JSON
files/extracted/rev_wayback/V117312<hash redacted>ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=<hash redacted>, for GNU/Linux 4.4.0, not stripped
files/extracted/rev_wayback/decrypt.py1383<hash redacted>Python script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T07:34:03Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T07:34:03Zartifact inventoryanalysis/artifact-inventory.json3 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T07:34:41Zhypothesis recordedhypothesis-board.mdReimplement V1 keygen and brute force Unix time seeds between 2013-12-10 and 2013-12-11 for length 20 with symbols and numbers, then decrypt AES-CBC ciphertextMediumDisassemble generate_password/main, validate reimplementation against the Linux binary in Docker, then test candidates against decrypt.py padding/flag format
2026-06-12T07:34:42Zcheckpoint recordedanalysis/checkpoint-triage-20260612T073442270981Z-4faa9f13.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-12T07:35:06Zlocal memory searchanalysis/research/local-memory-search-20260612T073506489277Z-b13d5f1d.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-12T07:35:38ZRAG queryanalysis/rag/rag-query-20260612T073506490829Z-19e22632.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-12T07:38:36ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-12T07:39:11Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-12T07:42:24Zinstrumentation plananalysis/instrumentation-plan.mdValidate V1 generator reconstruction against the Linux binary, then replay the 2013-12-10 to 2013-12-11 calendar-second seed window and decrypt AES-CBC candidates.HighStop if generator validation fails, Docker/glibc is unavailable, or the two-day search completes without a valid HTB-format plaintext; do not switch to arbitrary brute force without new evidence.
2026-06-12T07:42:24Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T07:42:35Zevaluatoranalysis/evaluator-20260612T074235394831Z-a2f9fefc.mdProceedHighRun exploit gate, generator validation, then bounded decrypt search.
2026-06-12T07:43:41Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T07:45:41Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • V1 is a Linux/glibc C++ generator; candidate replay must use glibc srand/rand.
  • The charset for the challenge path is lowercase + uppercase + !@#$%^&*_+ + digits, length 72.
  • The seed is equivalent to YYYYMMDDhhmmss modulo 2^32 from localtime.
  • The generator reconstruction matched the real binary in analysis/local/generator-validation.txt.
  • The valid decrypting candidate was found at calendar second 2013-12-11T13:01:25; non-sensitive metadata is in analysis/solution-summary.json.

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: Wayback
  • 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. Reverse the Linux C++ key generator to recover its character set construction and seed formula.
  2. Identify the seed as calendar time YYYYMMDDhhmmss modulo 2^32, passed to glibc srand.
  3. Reproduce glibc rand() in a Linux environment and validate generated output against the binary.
  4. Search the scenario's two-day calendar window for length-20 <password redacted> using lowercase, uppercase, symbols, and digits.
  5. Use each candidate as the null-padded <secret redacted> key for the bundled decryptor ciphertext until a valid HTB-format plaintext is recovered.

Reusable Lessons

  • Do not replay glibc rand() with macOS rand() or Python random; use Linux/glibc directly or a proven exact implementation.
  • A timestamp-seeded key generator can collapse a large candidate space into a bounded calendar-second search.
  • Validate a reconstructed keygen against the original binary before using it for flag recovery.

Dead Ends

  • No meaningful dead branches. RAG output was generic and not used as evidence.

Tool Quirks

  • The local sh PATH selected a Python interpreter without cryptography; /opt/homebrew/bin/python3 had the required package.
  • V1 needed a recent Linux/glibc userspace. python:3.12-slim worked; ubuntu:20.04 was too old for the binary's required GLIBC/GLIBCXX versions.
  • Docker was used only to provide Linux/glibc PRNG behavior; final decrypt logic ran locally.

Evidence Paths

  • analysis/source-audit.md
  • analysis/local/V1-main-disasm.txt
  • analysis/local/V1.rodata.txt
  • analysis/local/generator-validation.txt
  • analysis/solution-summary.json
  • 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
1Reimplement V1 password generator and brute force Unix time seeds between 2013-12-10 and 2013-12-11 for length 20 with symbols and numbers, then decrypt AES-CBC ciphertextV1 imports srand/rand/localtime; strings show character sets and prompts for length, symbols, numbers; decrypt.py uses candidate password as AES key padded to 32 bytesDisassemble generate_password/main, validate reimplementation against the Linux binary in Docker, then test candidates against decrypt.py padding/flag formatMediumActive

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 Wayback, 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.