Magic Vault
hackthebox
Task: Unlock a Solidity vault with contradictory require() conditions and claim content. Solution: Read private passphrase from storage, exploit uint128/uint64 truncation bypass, replicate blockhash-based randomness in attacker contract for atomic unlock.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Magic Vault — HackTheBox
Description
Alex walked through the monster's dorm, her sword drawn. As she explored, she found a small room hidden behind a heavy curtain. Inside, she saw a small, dark chamber with a single chest in the center. It was the magic vault she had heard rumors of, containing powerful magical artifacts. Excited, Alex approached the chest and touched it, feeling the pulsing energy that radiated from it. But as she tried to open it, she realized it was locked tight by powerful magic. Frustrated, she set out to find the key that would unlock it.
Goal: Call claimContent() so that mapHolder() returns an address different from address(TARGET). To do this, you first need to unlock the vault via unlock() with the correct password.
Analysis
Contracts
Setup.sol — standard setup, deploys Vault. Checks solution: isSolved() returns true when TARGET.mapHolder() != address(TARGET).
Vault.sol — main contract with multi-level locking system:
contract Vault { struct Map { address holder; } Map map; address public owner; bytes32 private passphrase; uint256 public nonce; bool public isUnlocked; constructor() { owner = msg.sender; passphrase = bytes32(keccak256(abi.encodePacked(uint256(blockhash(block.timestamp))))); map = Map(address(this)); } function unlock(bytes16 _password) public { uint128 _secretKey = uint128(bytes16(_magicPassword()) >> 64); uint128 _input = uint128(_password); require(_input != _secretKey, "Case 1 failed"); require(uint64(_input) == _secretKey, "Case 2 failed"); require(uint64(bytes8(_password)) == uint64(uint160(owner)), "Case 3 failed"); isUnlocked = true; } function claimContent() public { require(isUnlocked); map.holder = msg.sender; } function _generateKey(uint256 _reductor) private returns (uint256 ret) { ret = uint256(keccak256(abi.encodePacked(uint256(blockhash(block.number - _reductor)) + nonce))); nonce++; } function _magicPassword() private returns (bytes8) { uint256 _key1 = _generateKey(block.timestamp % 2 + 1); uint128 _key2 = uint128(_generateKey(2)); bytes8 _secret = bytes8(bytes16(uint128( uint128(bytes16(bytes32(uint256(uint256(passphrase) ^ _key1)))) ^ _key2 ))); return (_secret >> 32 | _secret << 16); } }
Key Observations
...
$ grep --similar
Similar writeups
- [blockchain][free]False Bidding— hackthebox
- [blockchain][free]Honor Among Thieves— hackthebox
- [blockchain][free]Token to Wonderland— hackthebox
- [blockchain][free]Locked and Loaded— hackthebox
- [blockchain][free]Portal Noncense— HackTheBox