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
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.
Walkthrough flow
Audit the ELF and recover the intended login path.
Use authenticated support-message functionality to...
Convert controlled wide-character input into 16-bit...
Use a second wide-character stack overflow in the...
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.
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.jsonrecords the provided ZIP and extracted binary.files/extracted/pwn_evil_corp/evil-corpis a 64-bit Linux PIE executable, dynamically linked and not stripped.analysis/source-audit.mdcontains 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:
SupportMsgat0x10000, writable.AssemblyTestPageat0x11000, 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:
- Connects to the live service.
- Logs in as
eliotwith password4007. - Selects support contact.
- Sends 2048 filler wide characters followed by UTF-8 codepoints whose low 16-bit values encode Linux x86-64 shellcode.
- Logs out.
- Starts a new login and sends 86 filler wide characters plus
U+11000, then half-closes the socket to force EOF termination. - 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c738a-d5e1-415e-8f8e-b9eec221e834.zip | 5641 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 2 shown in artifact inventory JSON |
files/extracted/pwn_evil_corp/evil-corp | 21592 | <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
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T16:10:56Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T16:10:56Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T16:11:06Z | artifact inventory | analysis/artifact-inventory.json | 2 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T16:16:01Z | hypothesis recorded | hypothesis-board.md | Login 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. | Medium | 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. |
| 2026-06-12T16:16:46Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T16:17:03Z | RAG query | analysis/rag/rag-query-20260612T161646756195Z-eeedc48b.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-12T16:17:05Z | local memory search | analysis/research/local-memory-search-20260612T161705425425Z-fba2725b.md | Found 5 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-12T16:17:48Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-12T16:17:48Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-12T16:17:48Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-12T16:18:06Z | instrumentation plan | analysis/instrumentation-plan.md | Validate 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. | High | Stop 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:06Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T161806715419Z-9f5b330a.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T16:18:54Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-12T16:19:20Z | evaluator | analysis/evaluator-20260612T161920187702Z-9a5eaacd.md | Proceed | High | Create solve/solve.py, run exploit gate, then execute bounded remote attempt. |
| 2026-06-12T16:21:18Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T16:23:22Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- Direct audit in
analysis/source-audit.mdidentifies the active exploit path.SetupmapsSupportMsgat fixed0x10000andAssemblyTestPageat fixed0x11000with RWX permissions. ContactSupportcopies up to0x1000wide characters intoSupportMsgas 16-bit words; after 2048 input characters, controlled bytes land in the executable page at0x11000.Loginreads up to0x12cwide characters into a0x150-byte stack frame. The saved return address is reached at 86 wide characters from the login-input buffer.- Planned exploit chain: authenticate with recovered
.rodatacredentials, stage direct file-read shellcode through support-message overflow, logout, then EOF-terminate a login-input overflow so execution returns to0x11000.
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: 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.
- Audit the ELF and recover the intended login path.
- Use authenticated support-message functionality to exploit a wide-character conversion bug.
- Convert controlled wide-character input into 16-bit instruction words that overflow from a fixed support buffer into an adjacent fixed RWX page.
- Use a second wide-character stack overflow in the login password path to return to the staged executable page.
- 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
fgetwstargets, count arguments are in wide characters, not bytes. Compare them against the actual byte-sized stack frame. - A
wchar_tto 16-bit conversion can be a controlled encoder when the locale accepts UTF-8 codepoints. - Fixed
mmapregions 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
fgetwswrite 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-8is unavailable, because non-ASCII codepoints may not decode throughfgetws. - 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.jsonanalysis/source-audit.mdanalysis/instrumentation-plan.mdanalysis/evaluator-20260612T161920187702Z-9a5eaacd.mdsolve/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 | Login 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
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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.