blockchainfreemedium

Portal Noncense

HackTheBox

**Goal:** Make `isPortalActive["orcKingdom"]` return `true` so that `Setup.isSolved()` passes.

$ ls tags/ techniques/
deterministic_address_precomputationnonce_grindingdelegatecall_storage_hijackstorage_layout_matching

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Portal Noncense — HackTheBox

Description

The group, who had gathered all the keys, found themselves at the portal station, trying to find a way to get to the Orc Kingdom, where the treasure was located according to the map. But as they looked around, they realized that the expert who usually helped them navigate the portals was nowhere to be found. They need to reverse engineer the process and figure out how to spawn a portal to their destination.

Goal: Make isPortalActive["orcKingdom"] return true so that Setup.isSolved() passes.

Analysis

Contracts

Setup.sol — deploys PortalStation and checks if TARGET.isPortalActive("orcKingdom") is true.

Portal.sol — main contract with two methods to activate portals:

contract PortalStation { mapping(string => address) public destinations; mapping(string => bool) public isPortalActive; bool isExpertStandby; constructor() { destinations["orcKingdom"] = 0xFC31cde4aCbF2b1d2996a2C7f695E850918e4007; destinations["elfKingdom"] = 0x598136Fd1B89AeaA9D6825086B6E4cF9ad2BD4cF; destinations["dawrfKingdom"] = 0xFc2D16b59Ec482FaF3A8B1ee6E7E4E8D45Ec8bf1; isPortalActive["elfKingdom"] = true; } function requestPortal(string calldata _destination) public payable { require(destinations[_destination] != address(0)); require(isExpertStandby, "Portal expert has a day off"); require(msg.value > 1337 ether); isPortalActive[_destination] = true; } function createPortal(string calldata _destination) public { require(destinations[_destination] != address(0)); (bool success, bytes memory retValue) = destinations[_destination].delegatecall( abi.encodeWithSignature("connect()") ); require(success, "Portal destination is currently not available"); require(abi.decode(retValue, (bool)), "Connection failed"); } }

Attack Vector Analysis

Path 1: requestPortal() — Dead end:

  • Requires isExpertStandby to be true (it's false, no setter exists)
  • Requires msg.value > 1337 ether (player only has ~15 ETH)

Path 2: createPortal() — Exploitable:

  • Does delegatecall to destinations["orcKingdom"] = 0xFC31cde4aCbF2b1d2996a2C7f695E850918e4007
  • This address has no code deployed on the challenge chain
  • The challenge name "Portal Noncense" hints at nonce manipulation

Key Insight: Deterministic CREATE Addresses

Ethereum contract addresses created via CREATE opcode are deterministic:

address = keccak256(rlp(deployer_address, nonce))[12:]

...

$ grep --similar

Similar writeups