Challenge / Pwn

Portaloo

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

MediumPublished 2025-04-10Sanitized local writeup

Scenario

Portaloo attack path

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

Portaloo sanitized attack graph

Walkthrough flow

01

Binary triage

02

Memory primitive

03

Control-flow hijack

04

Proof captured

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/Portaloo/writeup.md
  • htb-challenge/Pwn/Portaloo/notes.md
  • htb-challenge/Pwn/Portaloo/memory-summary.md
  • htb-challenge/Pwn/Portaloo/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Portaloo
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid

Summary

Portaloo is a hybrid pwn challenge built around a portal-management menu. The

binary is a PIE ELF with stack canaries, NX stack, full RELRO, and a provided

glibc, but its own create_portal path marks the heap page as executable.

The working exploit chain is:

  1. Create a portal so the heap page is marked RWX.
  2. Free that portal without clearing the global slot.
  3. Use the dangling slot and peek_into_the_void to leak tcache heap metadata,

recovering the heap page.

  1. Reallocate the same chunk through the second portal slot and write a compact

first-stage heap shellcode.

  1. Use the first step_into_the_portal read to leak the stack canary suffix.
  2. Use the second step_into_the_portal read to restore the canary and return

into the executable heap chunk.

  1. Stage /bin/sh shellcode and read the flag.

Artifact Inventory

Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.

  • files/a12c735b-04fc-46d4-9823-1e7a5b565d55.zip: original HTB archive.
  • files/extracted/portaloo: challenge ELF, not stripped.
  • files/extracted/glibc/: supplied loader and libc.
  • analysis/objdump-portaloo.txt: function-level disassembly used for source

audit.

  • analysis/leak-probe.txt: bounded live validation of heap and canary leaks.
  • solve/probe_leaks.py: leak-validation helper.
  • solve/solve.py: final reproducible exploit.

Analysis

Static disassembly showed five menu actions: create, destroy, upgrade, peek, and

step. The important properties are all visible in analysis/objdump-portaloo.txt:

  • create_portal allocates 0x20 bytes and calls mprotect on the aligned

heap page with permission 7, making that heap page executable.

  • destroy_portal calls free(slots[index]) but leaves the global slot value

unchanged.

  • peek_into_the_void prints up to 0x15 bytes from each non-null slot, so it

can read metadata from a freed tcache chunk.

  • upgrade_portal writes 0x15 bytes into an existing slot, including a chunk

reallocated from the freed portal.

  • step_into_the_portal uses a 0x48 stack buffer, reads 0x50 bytes, prints

it as a string, clears the buffer, and then reads 0x68 bytes before the

canary check.

The first step_into_the_portal read can be made to write one byte past the

buffer into the canary's leading null byte. The following %s print then leaks

the remaining seven canary bytes. The final read restores the full canary and

overwrites the saved return address.

The freed portal leak gives the heap page via glibc safe-linking metadata. The

first portal allocation was reused at page offset 0x2a0, which let the final

payload return directly into the first-stage shellcode stored in the executable

heap chunk.

Solve

Run:

bash
cd <local workspace>
python3 scripts/challenge_exec.py Pwn/Portaloo --phase exploit --output analysis/flag-candidate.txt -- python3 Pwn/Portaloo/solve/solve.py --host <TARGET> --port 30555 --output Pwn/Portaloo/analysis/flag-candidate.txt --debug-output Pwn/Portaloo/analysis/exploit-debug.txt
python3 scripts/challenge_harness.py capture-flag Pwn/Portaloo --from analysis/flag-candidate.txt

The solver derives the per-connection heap page and canary dynamically, writes

the final candidate to analysis/flag-candidate.txt, and stores the raw flag

only through the harness capture step.

Flag

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

Lessons

  • The challenge intentionally pairs strict normal mitigations with one unsafe

design choice: making the heap executable.

  • The dangling slot is useful twice: first as a tcache metadata leak, then as a

way to reclaim the same executable chunk.

  • For %s-based canary leaks, overwriting only the canary's leading null byte

is enough; the final payload must restore the full canary.

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: Portaloo
  • Category: Pwn
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-11T22:39:51Z
  • 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/a12c735b-04fc-46d4-9823-1e7a5b565d55.zip1073400<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 3 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-11T22:39:51Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-11T22:39:51Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-11T22:42:37Zhypothesis recordedhypothesis-board.mdUse portal UAF/tcache metadata plus step_into_the_portal overflow: create a portal to mprotect the heap RWX, free/peek a portal to recover heap page information, use the first step_into_the_portal read to leak canary/PIE, then use the second read to return into controlled heap shellcode.MediumRun a bounded remote transcript to confirm freed portal data leaks heap-like bytes and that a 0x49-byte first step read leaks the canary suffix without immediately terminating.
2026-06-11T22:42:37Zhypothesis recordedhypothesis-board.mdUse step_into_the_portal canary/PIE leak for a two-stage ROP leak, then ret2libc system('/bin/sh') with the provided libc.MediumIf heap-shellcode path fails, check whether leaked return address and available libc/main-binary gadgets can call puts/read or use a stack pivot.
2026-06-11T22:42:56Zcheckpoint recordedanalysis/checkpoint-analysis-20260611T224256549561Z-bbf433f1.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-11T22:44:18Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-11T22:44:18Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-11T22:44:18Zinstrumentation plananalysis/instrumentation-plan.mdValidate leak primitives and build a reproducible exploit for Portaloo.HighStop after two remote exploit attempts without a new leak or control-flow fact; record failure and rerank hypotheses.
2026-06-11T22:44:28Zevaluatoranalysis/evaluator-20260611T224428070455Z-c2e95976.mdValidate firstHighBuild leak validation script and run it through challenge_exec.
2026-06-11T22:44:51Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-11T22:44:59Zevaluatoranalysis/evaluator-20260611T224459698582Z-67a1ff28.mdProceedHighRun solve/probe_leaks.py through challenge_exec and inspect transcript.
2026-06-11T22:45:18Zleak validationanalysis/leak-probe.txtRemote service leaked freed-portal heap metadata and the stack canary suffix as predictedHighBuild final heap-shellcode exploit
2026-06-11T22:47:10Zexploitanalysis/flag-candidate.txtFinal solver returned into executable heap shellcode and captured an HTB-format flag candidateHighCapture flag through harness
2026-06-11T22:49:29Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-11T22:50:40Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • create_portal marks the aligned heap page RWX after the first allocation.
  • destroy_portal frees portal chunks but leaves slots[index] dangling.
  • peek_into_the_void can print freed tcache metadata, revealing the heap page.
  • step_into_the_portal leaks the canary suffix with a one-byte canary overwrite during the first read.
  • The final exploit restores the canary and returns to first-stage shellcode in the reused portal chunk at heap page offset 0x2a0.

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: Portaloo
  • 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.

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.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Use portal UAF/tcache metadata plus step_into_the_portal overflow: create a portal to mprotect the heap RWX, free/peek a portal to recover heap page information, use the first step_into_the_portal read to leak canary/PIE, then use the second read to return into controlled heap shellcode.Static disassembly shows create_portal malloc(0x20) and mprotect(aligned heap page, page_size, 7), destroy_portal frees without clearing slots, peek prints data from non-null slot pointers, and step_into_the_portal reads 0x50 then 0x68 into a 0x48-byte stack buffer with stack canary.Run a bounded remote transcript to confirm freed portal data leaks heap-like bytes and that a 0x49-byte first step read leaks the canary suffix without immediately terminating.MediumActive
2Use step_into_the_portal canary/PIE leak for a two-stage ROP leak, then ret2libc system('/bin/sh') with the provided libc.Provided libc is present and binary has PLT/GOT entries, but exported-gadget scan shows very limited simple pop gadgets in the main binary, making direct ROP less likely than heap shellcode.If heap-shellcode path fails, check whether leaked return address and available libc/main-binary gadgets can call puts/read or use a stack pivot.MediumActive

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