← Back to Writeups
CUSTOMMediumBlockchain

[Blackbox] Web3_VIP

XESXOR9/2/20262 min read
#blockchain#custom#medium#bytecode_analysis#ecdsa_signature_malleability#eip712_typed_data#signature_replay_bypass#storage_slot_reading#linux

[Blackbox] Web3_VIP

Platform: caplag | Category: Blockchain | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-02-22 | Status: Solved Techniques: bytecode_analysis, ecdsa_signature_malleability, eip712_typed_data, signature_replay_bypass, storage_slot_reading

Summary

Task: Claim a VIP NFT using an admin attestation signature, but the only known signature was already used. Solution: Exploit ECDSA signature malleability — the contract uses raw ecrecover without low-s enforcement and tracks used signatures by raw bytes hash, so a malleable signature (r, n-s, flip(v)) bypasses replay protection.

[Blackbox] Web3_VIP — CapLag

Description

A mini DeFi protocol stack (Bridge, VIP, Staking, Lending, Governance) is presented. The architecture includes an Nginx frontend, a backend server, and a local Anvil (Foundry) network as the blockchain environment. The task is specifically about the VIP component — claiming a VIP NFT membership using an admin attestation signature.

Goal: Obtain a VIP NFT by calling claimVIP() on the AccessManager contract, despite the only known signature having already been used. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

Reconnaissance

From the frontend (/js/app.js), the AccessManager contract ABI was extracted:

  • claimVIP(uint256 nonce, bytes signature) returns (uint256)
  • balanceOf(address who) view returns (uint256)

From /runtime/config.json:

  • Contract address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  • Nonce: 42
  • Used signature: 0x6ed999...135a1b

Vulnerability

Bytecode analysis of the contract revealed:

  • Uses EIP-712 typed data signing (domain: "CapLag VIP Pass", version "1")
  • Verifies signature via raw ecrecover without low-s enforcement
  • Tracks used signatures via mapping(bytes32 => bool) by keccak256 of the raw signature bytes ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

ECDSA Signature Malleability: For any valid signature (r, s, v), there exists an equivalent (r, n-s, flip(v)). The contract does not check s canonicity, and replay protection is based on raw bytes hash — a malleable signature bypasses both checks.

Solution

python
# secp256k1 curve order
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

# Original signature (from config.json)
r = 0x6ed999961388ab2be9369503d9b9d7d22fe54cdc48b81d49bd03926ce50232b0
s = 0x6f4be70333b24d2b55709254301578a3570d7fdb5204173ac3195bcab604135a
v = 27
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Malleable signature
new_s = n - s  # 0x90b418fccc4db2d4aa8f6dabcfea875b63a15d0b5d448900fcb902c21a322de7
new_v = 28
bash
cast send 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 \
  "claimVIP(uint256,bytes)" 42 \
  "0x6ed999961388ab2be9369503d9b9d7d22fe54cdc48b81d49bd03926ce50232b090b418fccc4db2d4aa8f6dabcfea875b63a15d0b5d448900fcb902c21a322de71c" \
  --private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
  --rpc-url http://not-web.caplag-task.ru:31483

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍