Challenge / Pwn

Evil Copr

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

MediumPublished 2025-03-15Sanitized local writeup

Scenario

Evil Copr attack path

Evil Copr 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 Pwn evidence, validation, and reusable operator lessons.

Evil Copr sanitized attack graph

Walkthrough flow

01

Audit the ELF and recover the intended login path.

02

Use authenticated support-message functionality to...

03

Convert controlled wide-character input into 16-bit...

04

Use a second wide-character stack overflow in the...

05

Prefer direct file-read shellcode over an interactive...

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.

  • Pwn/Evil-Copr/writeup.md
  • htb-challenge/Pwn/Evil-Copr/notes.md
  • htb-challenge/Pwn/Evil-Copr/memory-summary.md
  • htb-challenge/Pwn/Evil-Copr/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Evil-Copr
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid

Summary

Evil-Copr is a wide-character pwn challenge built around two developer mistakes. The support form converts wide-character input into 16-bit output inside a fixed mapping, which can overflow into an adjacent fixed RWX page. A separate password read in Login overflows the stack. Chaining both bugs gives reliable control: first stage shellcode into the RWX page, then logout and return to it through the login overflow.

Artifact Inventory

  • analysis/artifact-inventory.json records the provided ZIP and extracted binary.
  • files/extracted/pwn_evil_corp/evil-corp is a 64-bit Linux PIE executable, dynamically linked and not stripped.
  • analysis/source-audit.md contains the disassembly-backed exploit reasoning.

Analysis

Static analysis showed plain credentials in .rodata: eliot / 4007. Those credentials unlock the menu and allow access to ContactSupport.

Setup creates two fixed mappings:

  • SupportMsg at 0x10000, writable.
  • AssemblyTestPage at 0x11000, readable, writable, and executable.

ContactSupport reads up to 0x1000 wide characters and calls wcharToChar16, which stores each input wchar_t as a 16-bit word into SupportMsg. After 2048 input characters, the copy reaches 0x11000, so controlled 16-bit words become executable code.

Login allocates 0x150 stack bytes but reads the password with fgetws(..., 0x12c, stdin). The saved return address is reached at 86 wide characters from the password buffer. Sending codepoint U+11000 at that offset writes the low return-address dword as 0x00011000. EOF immediately after that codepoint makes fgetws append a NUL wchar_t, clearing the high dword.

RAG/local memory did not provide a challenge-specific hint. The decision to proceed was based on direct source evidence in analysis/source-audit.md.

Solve

The final solver is solve/solve.py.

The script:

  1. Connects to the live service.
  2. Logs in as eliot with password 4007.
  3. Selects support contact.
  4. Sends 2048 filler wide characters followed by UTF-8 codepoints whose low 16-bit values encode Linux x86-64 shellcode.
  5. Logs out.
  6. Starts a new login and sends 86 filler wide characters plus U+11000, then half-closes the socket to force EOF termination.
  7. The shellcode opens /flag.txt, writes the result to stdout, and exits.

The successful output was saved to loot/flag-candidate.txt and captured through the harness into loot/flag.txt.

Flag

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

Lessons

  • Wide-character input can be exploitable in two separate ways: through byte-width conversion bugs and through larger-than-expected stack writes.
  • EOF termination is useful when a wide-character overflow needs to write a zero dword that cannot be supplied directly through normal line input.
  • For pwn challenges, direct file-read shellcode is more reliable than spawning an interactive shell when the exploit must close stdin to shape memory.

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: Evil-Copr
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-12T16:10:56Z
  • 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/a12c738a-d5e1-415e-8f8e-b9eec221e834.zip5641<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON
files/extracted/pwn_evil_corp/evil-corp21592<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 3.2.0, not stripped

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T16:10:56Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T16:10:56Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T16:11:06Zartifact inventoryanalysis/artifact-inventory.json2 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T16:16:01Zhypothesis recordedhypothesis-board.mdLogin with recovered credentials, use Contact Support wide-char conversion overflow to write code into fixed RWX AssemblyTestPage at 0x11000, then logout and use the Login wide-character login-input stack overflow to return to 0x11000.MediumBuild a bounded Python solver that stages code with ContactSupport, then EOF-terminates a Login overflow so the NUL terminator clears the high dword of the return address.
2026-06-12T16:16:46Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T16:17:03ZRAG queryanalysis/rag/rag-query-20260612T161646756195Z-eeedc48b.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-12T16:17:05Zlocal memory searchanalysis/research/local-memory-search-20260612T161705425425Z-fba2725b.mdFound 5 safe prior-note result(s)MediumRecord useful result or skip
2026-06-12T16:17:48ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-12T16:17:48Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-12T16:17:48Zresearch recordanalysis/research/research-records.mdResearch tagged GENERICMediumValidate against current evidence
2026-06-12T16:18:06Zinstrumentation plananalysis/instrumentation-plan.mdValidate and run the two-stage Evil-Copr exploit without blind remote probing: stage direct file-read shellcode into fixed RWX page 0x11000, then return to it via Login login-input overflow.HighStop after two non-flag remote attempts or any crash/no-output result that does not add a new fact; record failure and re-evaluate offsets/locale assumptions.
2026-06-12T16:18:06Zcheckpoint recordedanalysis/checkpoint-analysis-20260612T161806715419Z-9f5b330a.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-12T16:18:54Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T16:19:20Zevaluatoranalysis/evaluator-20260612T161920187702Z-9a5eaacd.mdProceedHighCreate solve/solve.py, run exploit gate, then execute bounded remote attempt.
2026-06-12T16:21:18Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T16:23:22Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Direct audit in analysis/source-audit.md identifies the active exploit path. Setup maps SupportMsg at fixed 0x10000 and AssemblyTestPage at fixed 0x11000 with RWX permissions.
  • ContactSupport copies up to 0x1000 wide characters into SupportMsg as 16-bit words; after 2048 input characters, controlled bytes land in the executable page at 0x11000.
  • Login reads up to 0x12c wide characters into a 0x150-byte stack frame. The saved return address is reached at 86 wide characters from the login-input buffer.
  • Planned exploit chain: authenticate with recovered .rodata credentials, stage direct file-read shellcode through support-message overflow, logout, then EOF-terminate a login-input overflow so execution returns to 0x11000.

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: Pwn
  • Challenge: Evil-Copr
  • 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. Audit the ELF and recover the intended login path.
  2. Use authenticated support-message functionality to exploit a wide-character conversion bug.
  3. Convert controlled wide-character input into 16-bit instruction words that overflow from a fixed support buffer into an adjacent fixed RWX page.
  4. Use a second wide-character stack overflow in the login password path to return to the staged executable page.
  5. Prefer direct file-read shellcode over an interactive shell when the final overflow needs EOF termination to shape the return address.

Reusable Lessons

  • In Linux fgetws targets, count arguments are in wide characters, not bytes. Compare them against the actual byte-sized stack frame.
  • A wchar_t to 16-bit conversion can be a controlled encoder when the locale accepts UTF-8 codepoints.
  • Fixed mmap regions with adjacent writable and executable permissions can turn a non-control-data overflow into a shellcode staging primitive.
  • EOF after a final non-ASCII codepoint can make fgetws write a NUL terminator exactly where a zero dword is needed.

Dead Ends

  • No independent challenge-specific RAG memory was found; RAG returned generic pwn guidance only.
  • The disabled menu option for the assembly tester is not the trigger. The working trigger is the separate login return-address overwrite.

Tool Quirks

  • Local Docker reproduction can fail if en_US.UTF-8 is unavailable, because non-ASCII codepoints may not decode through fgetws.
  • The local tool readiness check showed common pwn tools missing, but objdump, Python, and direct socket interaction were sufficient for this solve.

Evidence Paths

  • analysis/artifact-inventory.json
  • analysis/source-audit.md
  • analysis/instrumentation-plan.md
  • analysis/evaluator-20260612T161920187702Z-9a5eaacd.md
  • 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
1Login with recovered credentials, use Contact Support wide-char conversion overflow to write code into fixed RWX AssemblyTestPage at 0x11000, then logout and use the Login wide-character password stack overflow to return to 0x11000.Objdump shows SupportMsg mmap at fixed 0x10000 size 0x4b0, AssemblyTestPage mmap at fixed 0x11000 size 0x800 with <secret redacted>\<secret redacted>\<secret redacted>, ContactSupport copies up to 0x1000 wide chars into SupportMsg as 16-bit words, and Login reads 0x12c wide chars into a 0x150-byte stack frame.Build a bounded Python solver that stages code with ContactSupport, then EOF-terminates a Login overflow so the NUL terminator clears the high dword of the return address.

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Technical analogy

How to remember this solve

Think of the challenge as a small system with one rule that matters more than the rest. The solve is finding that rule, validating it, and using it carefully enough to reach the final proof.

For Evil Copr, 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.