Simple Encryptor
Simple Encryptor is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Simple Encryptor attack path
Simple Encryptor 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
Binary triage
Control-flow recovery
Key logic reconstruction
Proof captured
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.
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/Simple-Encryptor/writeup.md
- htb-challenge/Reversing/Simple-Encryptor/notes.md
- htb-challenge/Reversing/Simple-Encryptor/memory-summary.md
- htb-challenge/Reversing/Simple-Encryptor/hypothesis-board.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Reversing__Simple-Encryptor__memory-summary.md.2ccf0722e0.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Reversing__Simple-Encryptor__notes.md.1c08ff05fc.md
Technical Walkthrough
Writeup
Challenge
- Name: Simple-Encryptor
- Category: Reversing
- Difficulty: Very Easy
- Mode: file
Summary
The binary stores a 4-byte Unix time seed at the start of flag.enc, then uses glibc's rand() stream twice per plaintext byte: first to XOR the byte with rand() & 0xff, then to rotate the result left by rand() & 7. Replaying the same glibc PRNG sequence and reversing the operations (ROR then XOR) decrypts the flag deterministically.
The encrypted file starts with a 4-byte Unix timestamp seed. The encrypt binary uses that seed with libc srand(), then for each plaintext byte XORs with rand() & 0xff and rotates left by rand() & 7. Replaying the same PRNG and applying the inverse operations (ROR then XOR) decrypts the flag deterministically.
Artifact Inventory
files/a12c7354-6ee3-4584-912e-2f8b1c122ecf.zip— original encrypted archive.analysis/<password redacted>— successful archive decryption validation.analysis/extracted/rev_simpleencryptor/encrypt— ELF encryptor binary.analysis/extracted/rev_simpleencryptor/flag.enc— encrypted flag blob.analysis/objdump-disasm.txt— disassembly used to reconstruct the transform.analysis/solve-result.txt— recovered flag before harness capture.solve/solve.py— deterministic decryptor.
files/a12c7354-6ee3-4584-912e-2f8b1c122ecf.zip— original encrypted archive.analysis/<password redacted>— successful archive decryption validation.analysis/extracted/rev_simpleencryptor/encrypt— ELF encryptor binary.analysis/extracted/rev_simpleencryptor/flag.enc— encrypted flag blob.analysis/objdump-disasm.txt— disassembly used to reconstruct the transform.analysis/solve-result.txt— recovered flag before harness capture.solve/solve.py— deterministic decryptor.
Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.
Analysis
main() opens the input file, reads it into memory, calls time(NULL) and srand(seed), writes the 4-byte seed to the start of the output file, and then for each plaintext byte calls rand() twice: once to obtain the XOR byte and a second time to derive the rotate-left amount via & 7. The output byte is therefore ROL(plain ^ (rand1 & 0xff), rand2 & 7). Decryption must replay the exact glibc random() stream from the stored seed, then apply ROR(cipher, rand2 & 7) ^ (rand1 & 0xff).
main() opens flag, reads it fully, seeds srand(time(NULL)), and writes the seed as the first four bytes of flag.enc. It then iterates each plaintext byte, XORing it with rand() & 0xff, stores that intermediate byte, calls rand() again, masks the result with 7, and rotates the byte left by that count. To decrypt, read the seed prefix, replay the same libc PRNG sequence, rotate each ciphertext byte right by the second random value, and XOR with the first random value.
Solve
The solver reads the first four bytes of flag.enc as a little-endian Unix timestamp seed, reconstructs glibc's additive random() sequence, and for each ciphertext byte applies a right rotate by the second random value and then XORs with the first random value. The resulting plaintext is the HTB flag.
The solver reads the first four bytes of flag.enc as a little-endian Unix timestamp seed, initializes libc rand() with srand(seed), and for each encrypted byte computes plain = ROR(cipher, rand() & 7) XOR (previous rand() & 0xff). The resulting plaintext is the HTB flag.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
For Linux binaries, be careful not to assume the host platform's libc PRNG matches the target. The first attempt using macOS libc failed; the correct decryptor had to reproduce glibc's random() algorithm exactly before the byte-wise inverse transform produced the flag.
When an encryptor stores its PRNG seed alongside ciphertext, reversing often reduces to reconstructing the exact byte transform and replaying the same RNG. Pay close attention to operation order—here it was XOR first, then rotate-left—because the inverse must unwind that sequence exactly.
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: Simple-Encryptor
- Category: Reversing
- Difficulty: Very Easy
- Mode: file
- Remote instance: none
- Start time: 2026-06-07T09:30:02Z
- 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/a12c7354-6ee3-4584-912e-2f8b1c122ecf.zip | 3723 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-07T09:30:02Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-07T09:30:02Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T09:30:02Z | session bootstrap | notes.md | Challenge metadata, scenario, and prior context seeded into workspace | High | Record initial hypothesis and research |
| 2026-06-07T09:30:02Z | hypothesis recorded | hypothesis-board.md | initial triage from supplied challenge metadata | Medium | inventory files / inspect app surface / map routes depending on category |
| 2026-06-07T09:30:03Z | research task | analysis/research/task-20260607T093003010511Z-1a9ded45.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-07T12:28:01Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-07T12:29:33Z | research skip | analysis/research/research-skip.md | Research intentionally skipped with recorded reason | Medium | Gate before exploit |
| 2026-06-07T12:29:50Z | checkpoint recorded | analysis/checkpoint-analysis-20260607T122950517333Z-fd3a7e76.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-07T12:30:33Z | checkpoint recorded | analysis/checkpoint-analysis-20260607T123033289577Z-10d6d427.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-07T12:30:33Z | Reconstructed and inverted PRNG/XOR/ROL transform | analysis/objdump-disasm.txt, analysis/solve-result.txt | The binary seeds libc rand() with the first 4 ciphertext bytes interpreted as a Unix timestamp, XORs each plaintext byte with rand() & 0xff, then rotates left by rand() & 7. Replaying the same PRNG and reversing with ROR+XOR decrypts the flag. | High | Capture the flag through the harness and complete the workspace. |
| 2026-06-07T12:32:31Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-07T12:32:31Z | Reconstructed glibc PRNG/XOR/ROL transform and decrypted flag.enc | analysis/objdump-disasm.txt, analysis/solve-result.txt | The binary stores a 4-byte Unix time seed in the ciphertext, uses glibc rand() twice per plaintext byte (xor then rol), and writes the transformed bytes to flag.enc. Replaying glibc's additive random stream and applying ROR then XOR decrypts the flag. | High | Capture the flag through the harness and complete the workspace. |
| 2026-06-07T12:32:31Z | 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.
Scenario
On our regular checkups of our secret flag storage server we found out that we were hit by ransomware. The original flag data is nowhere to be found, but luckily we not only have the encrypted file but also the encryption program itself.
Operator Question
Analyze the provided reversing challenge artifacts, reverse the encryption program, decrypt flag.enc reproducibly, and capture the HTB flag.
Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Reversing
- Challenge: Simple-Encryptor
- Difficulty: Very 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.
Reusable Lessons
-
Dead Ends
-
Tool Quirks
-
Evidence Paths
-
Ingestion Decision
- Proposed for LightRAG: yes/no
- 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 | initial triage from supplied challenge metadata | challenge name, category, difficulty, scenario, and remote target were provided by operator | inventory files / inspect app surface / map routes depending on category | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|
Memory Summary
approval_required: true
Sanitized Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Reversing
- Challenge: Simple-Encryptor
- Difficulty: Very 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.
Reusable Lessons
-
Dead Ends
-
Tool Quirks
-
Evidence Paths
-
Ingestion Decision
- Proposed for LightRAG: yes/no
- Requires user approval before ingestion: yes
Notes
Notes
Scope
- Challenge: Simple-Encryptor
- Category: Reversing
- Difficulty: Very Easy
- Mode: file
- Remote instance: none
- Start time: 2026-06-07T09:30:02Z
- 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/a12c7354-6ee3-4584-912e-2f8b1c122ecf.zip | 3723 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 3 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-07T09:30:02Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-07T09:30:02Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-07T09:30:02Z | session bootstrap | notes.md | Challenge metadata, scenario, and prior context seeded into workspace | High | Record initial hypothesis and research |
| 2026-06-07T09:30:02Z | hypothesis recorded | hypothesis-board.md | initial triage from supplied challenge metadata | Medium | inventory files / inspect app surface / map routes depending on category |
| 2026-06-07T09:30:03Z | research task | analysis/research/task-20260607T093003010511Z-1a9ded45.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-07T12:28:01Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-07T12:29:33Z | research skip | analysis/research/research-skip.md | Research intentionally skipped with recorded reason | Medium | Gate before exploit |
| 2026-06-07T12:29:50Z | checkpoint recorded | analysis/checkpoint-analysis-20260607T122950517333Z-fd3a7e76.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-07T12:30:33Z | checkpoint recorded | analysis/checkpoint-analysis-20260607T123033289577Z-10d6d427.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
2026-06-07T12: <REDACTED>, analysis/solve-result.txt | The binary seeds libc rand() with the first 4 ciphertext bytes interpreted as a Unix timestamp, XORs each plaintext byte with rand() & 0xff, then rotates left by rand() & 7. Replaying the same PRNG and reversing with ROR+XOR decrypts the flag. | High | Capture the flag through the harness and complete the workspace. | ||
| 2026-06-07T12: <REDACTED> | |||||
2026-06-07T12: <REDACTED>, analysis/solve-result.txt | The binary stores a 4-byte Unix time seed in the ciphertext, uses glibc rand() twice per plaintext byte (xor then rol), and writes the transformed bytes to flag.enc. Replaying glibc's additive random stream and applying ROR then XOR decrypts the flag. | High | Capture the flag through the harness and complete the workspace. | ||
| 2026-06-07T12:32:31Z | 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.
Scenario
On our regular checkups of our secret flag storage server we found out that we were hit by ransomware. The original flag data is nowhere to be found, but luckily we not only have the encrypted file but also the encryption program itself.
Operator Question
Analyze the provided reversing challenge artifacts, reverse the encryption program, decrypt flag.enc reproducibly, and capture the HTB flag.
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 Simple Encryptor, 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.