Wayback
Wayback is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
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.
Walkthrough flow
Reverse the Linux C++ key generator to recover its...
Identify the seed as calendar time YYYYMMDDhhmmss...
Reproduce glibc rand() in a Linux environment and...
Search the scenario's two-day calendar window for...
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.
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
0123456789if 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:
cd <local workspace>
/opt/homebrew/bin/python3 solve/solve.py --validate-current-binarySolve command:
cd <local workspace>
/opt/homebrew/bin/python3 solve/solve.pyThe 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/; useanalysis/solution-summary.jsonfor 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c733b-711e-4898-ad0c-e31539201d1f.zip | 5456 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
files/extracted/rev_wayback/V1 | 17312 | <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.py | 1383 | <hash redacted> | Python script text executable, ASCII text |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T07:34:03Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T07:34:03Z | artifact inventory | analysis/artifact-inventory.json | 3 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T07:34:41Z | hypothesis recorded | hypothesis-board.md | Reimplement 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 ciphertext | Medium | Disassemble generate_password/main, validate reimplementation against the Linux binary in Docker, then test candidates against decrypt.py padding/flag format |
| 2026-06-12T07:34:42Z | checkpoint recorded | analysis/checkpoint-triage-20260612T073442270981Z-4faa9f13.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-12T07:35:06Z | local memory search | analysis/research/local-memory-search-20260612T073506489277Z-b13d5f1d.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-12T07:35:38Z | RAG query | analysis/rag/rag-query-20260612T073506490829Z-19e22632.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-12T07:38:36Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-12T07:39:11Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-12T07:42:24Z | instrumentation plan | analysis/instrumentation-plan.md | Validate 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. | High | Stop 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:24Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T07:42:35Z | evaluator | analysis/evaluator-20260612T074235394831Z-a2f9fefc.md | Proceed | High | Run exploit gate, generator validation, then bounded decrypt search. |
| 2026-06-12T07:43:41Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T07:45:41Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
V1is a Linux/glibc C++ generator; candidate replay must use glibcsrand/rand.- The charset for the challenge path is lowercase + uppercase +
!@#$%^&*_++ digits, length 72. - The seed is equivalent to
YYYYMMDDhhmmssmodulo 2^32 fromlocaltime. - 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 inanalysis/solution-summary.json.
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: 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.
- Reverse the Linux C++ key generator to recover its character set construction and seed formula.
- Identify the seed as calendar time
YYYYMMDDhhmmssmodulo 2^32, passed to glibcsrand. - Reproduce glibc
rand()in a Linux environment and validate generated output against the binary. - Search the scenario's two-day calendar window for length-20 <password redacted> using lowercase, uppercase, symbols, and digits.
- 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 macOSrand()or Pythonrandom; 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
shPATH selected a Python interpreter withoutcryptography;/opt/homebrew/bin/python3had the required package. V1needed a recent Linux/glibc userspace.python:3.12-slimworked;ubuntu:20.04was 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.mdanalysis/local/V1-main-disasm.txtanalysis/local/V1.rodata.txtanalysis/local/generator-validation.txtanalysis/solution-summary.jsonsolve/solve.pyloot/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 | Reimplement 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 ciphertext | V1 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 bytes | Disassemble generate_password/main, validate reimplementation against the Linux binary in Docker, then test candidates against decrypt.py padding/flag format | Medium | 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 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.