Blue Box: Chain Heist - Incident Response Writeup Report

Case ID: BB-CHAIN-2026-0426
Classification: Confirmed Compromise - Off-Chain Signature Phishing (Permit2 Drain)
Analyst: Security Operations
Evidence: wallet_session.json, debug_traces.json, chain_export.json, address_book.json, mempool_capture.jsonl, abi_notes.md
Network: Ethereum Mainnet
Date of Analysis: 2026-04-26
Challenge Link: https://learn.hacklido.com/blue-box/chain-heist
1. Executive Summary
A wallet extension session log, mempool capture, and on-chain transaction export were analyzed to reconstruct a Permit2 signature-phishing attack. The victim was lured to a fake airdrop-claim site and signed a gasless eth_signTypedData_v4 Permit2 approval believing they were claiming rewards. The attacker used that signature to drain 186,420.55 USDC from the victim’s wallet, swapped the proceeds to WETH through Uniswap’s Universal Router, and bridged the funds to Arbitrum One via Across Protocol, all within roughly six minutes of the signature being approved.
Verdict: Confirmed theft via off-chain signature phishing. No malware or on-chain approval transaction was required by the attacker, the victim’s own signature was the only credential needed.
2. Tools Used
| Source File | Purpose |
wallet_session.json | Wallet extension activity log: tabs visited, signing request approved by the victim |
mempool_capture.jsonl | Pending-transaction observations correlating the signature to its on-chain use |
debug_traces.json | Internal call traces for each transaction (decoded method calls and arguments) |
chain_export.json | Finalized on-chain transactions, logs, and event data |
address_book.json | Address labeling and attacker cluster attribution |
abi_notes.md | Reference ABI/event definitions and token decimals used for manual decoding |
3. Methodology - Step by Step
Step 1: Identify the victim and the phishing vector
cat wallet_session.json | jq '.signing_requests[0].account, .tabs'
The wallet session log shows the victim’s account approved a signing request while two tabs were open: a legitimate Safe Wallet interface (app.safe.global) and a lookalike phishing page:
https://airdrop-safe-claims.example/claim?campaign=safe-airdrop-2026
The phishing tab was opened at 09:11:33Z, and the signature request metamask-req-8842 was approved at 09:12:44Z, roughly a minute later, timing consistent with a user clicking through a “claim rewards” flow on the malicious page.
Q1 - Victim wallet address: HackCTF{0x71C7656EC7ab88b098defB751B7401B5f6d8976F}
Q2 - Phishing domain that requested the typed-data signature: HackCTF{airdrop-safe-claims.example}
Step 2: Decode the signed typed data
cat wallet_session.json | jq '.signing_requests[0].typed_data'
The approved payload is a Permit2 PermitSingle structure:
domain.name: Permit2
domain.verifyingContract: 0x000000000022D473030F116dDEE9F6B43aC78BA3
message.details.token: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (USDC)
message.details.amount: 186420550000
message.spender: 0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1
This is a gasless, off-chain signature that authorizes the named spender contract to pull the specified token amount from the victim’s wallet via the canonical Uniswap Permit2 contract, at any point before sigDeadline, without requiring the victim to broadcast anything themselves. The spender address is cross-referenced in address_book.json as attacker-drainer-contract.
Q3 - Malicious Permit2 spender contract: HackCTF{0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1}
Step 3: Correlate the signature to its on-chain redemption
grep "metamask-req-8842" mempool_capture.jsonl
The mempool capture confirms the link directly:
tx_hash: 0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211
from: 0xA11ce00000000000000000000000000000000BEEF
to: 0x000000000022D473030F116dDEE9F6B43aC78BA3
decoded_method: permitTransferFrom
note: "Signature bytes matched wallet request metamask-req-8842."
Cross-referencing debug_traces.json for this hash shows the internal call unwinds into an ERC20 transferFrom moving funds directly from the victim to the attacker’s drainer contract:
CALL Permit2.permitTransferFrom
-> CALL USDC.transferFrom(from: victim, to: 0x9d9F7b..., amount: 186420550000)
chain_export.json confirms this transaction succeeded at block 19763112 and emitted a Transfer event moving 186420.55 USDC (6 decimals, per abi_notes.md) from the victim to the drainer contract, along with a NonceInvalidation event confirming the Permit2 nonce was consumed.
Note also that a decoy transaction (simulateClaim, hash 0x4f3b8d9e...) was broadcast from the victim’s own wallet moments earlier and reverted with no asset movement, this was surfaced by the phishing UI itself to make the victim believe they had “claimed” something, while the real theft occurred purely through the off-chain signature.
Q4 - Transaction hash that consumed the Permit2 signature and drained funds:
HackCTF{0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211}
Q5 - Transaction hash that consumed the Permit2 signature and drained funds:
HackCTF{0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211}
Q6 - USDC amount transferred from the victim (human amount, exact):
HackCTF{186420.55}
Q7 - Attacker-controlled EOA that submitted the drain transaction:
HackCTF{0xA11ce00000000000000000000000000000000BEEF}
Step 4: Trace the stolen funds through the swap
cat debug_traces.json | jq '."0xe2fd7c39a34140e48ea7e628efb7355e6aa2e4d1d395ec850cc6f3e549d2d0b4"'
Three minutes after the drain, the drainer contract called execute on the Uniswap Universal Router:
from: 0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1
to: 0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B (Uniswap Universal Router)
decoded_route: USDC -> WETH via Uniswap V3 0.05% pool
amount_in: 186420.55 USDC
amount_out: 82.418731 WETH
chain_export.json confirms two matching Transfer events in the same transaction: USDC moving from the drainer contract into the router, and WETH (18 decimals, per abi_notes.md) moving back out to the drainer contract.
Q8 - Router contract used to swap stolen USDC into WETH:
HackCTF{0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B}
Step 5: Trace the funds across the bridge
cat debug_traces.json | jq '."0x7dc8f4a9586678f21e9d3f9dfef29975fe8c14d50ac164738d5fd8447f411c92"'
The final leg shows the drainer contract calling depositV3 on the Across Protocol SpokePool:
from: 0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1
to: 0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5 (Across Protocol SpokePool)
inputToken: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 (WETH)
inputAmount: 82418731000000000000
destinationChainId: 42161
destinationChain: Arbitrum One
depositId: 772184
The corresponding V3FundsDeposited event in chain_export.json confirms the funds were routed to recipient 0x48b2b70c99A4D0bE2d3F1520F5fA0190E071d2A2 on Arbitrum One, moving the stolen assets off Ethereum mainnet to obscure the trail.
Q9 - Destination chain the swapped funds were bridged to: HackCTF{Arbitrum One}
Q10 - Deposit ID emitted by the bridge event: HackCTF{772184}
Step 6: Confirm attacker infrastructure clustering
cat address_book.json | jq '.cluster_notes'
cluster_id: nb-chainheist-cluster-17
members:
0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1 (drainer contract)
0xA11ce00000000000000000000000000000000BEEF (funding EOA)
0x48b2b70c99A4D0bE2d3F1520F5fA0190E071d2A2 (Arbitrum recipient)
rationale: shared funding source, sequential drain/swap/bridge behavior, recipient reuse on Arbitrum
This clustering ties the drainer contract, the funding EOA that submitted the drain transaction, and the final Arbitrum recipient together as the same attacker infrastructure based on behavioral and funding-flow analysis.
Q11 - Cluster ID assigned to the attacker infrastructure: HackCTF{nb-chainheist-cluster-17}
4. Consolidated Findings
| # | Question | Answer |
| 1 | Victim wallet address | 0x71C7656EC7ab88b098defB751B7401B5f6d8976F |
| 2 | Phishing domain | airdrop-safe-claims.example |
| 3 | Malicious Permit2 spender | 0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1 |
| 4 | Drain transaction hash | 0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211 |
| 5 | Drain transaction hash | 0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211 |
| 6 | USDC amount stolen | 186420.55 |
| 7 | Attacker funding EOA | 0xA11ce00000000000000000000000000000000BEEF |
| 8 | Swap router used | 0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B (Uniswap Universal Router) |
| 9 | Bridge destination chain | Arbitrum One |
| 10 | Bridge deposit ID | 772184 |
| 11 | Attacker cluster ID | nb-chainheist-cluster-17 |
5. Indicators of Compromise (IOCs)
- Phishing domain:
airdrop-safe-claims.example
- Malicious Permit2 spender / drainer contract:
0x9d9F7b9c23D2A6b612d1E4c54eE8b44a9A04d2a1
- Attacker funding EOA:
0xA11ce00000000000000000000000000000000BEEF
- Arbitrum recipient address:
0x48b2b70c99A4D0bE2d3F1520F5fA0190E071d2A2
- Attacker cluster:
nb-chainheist-cluster-17
- Drain transaction:
0x9a9f3d3a5d27dc643ce35139c9115f8b2d8b3c4c1f9c7950b1f9b7d876a4f211
- Swap transaction:
0xe2fd7c39a34140e48ea7e628efb7355e6aa2e4d1d395ec850cc6f3e549d2d0b4
- Bridge transaction:
0x7dc8f4a9586678f21e9d3f9dfef29975fe8c14d50ac164738d5fd8447f411c92
6. Recommendations
- Never sign
eth_signTypedData_v4 (Permit / Permit2) requests from sites reached through unsolicited links, especially “airdrop claim” pages. A signature alone can authorize a full token pull without any further confirmation.
- Verify the requesting domain matches the legitimate protocol before approving any wallet signature, not just before sending a transaction.
- Use wallet extensions or interfaces that clearly decode and display Permit2 typed-data details (token, amount, spender) before signing, and treat unfamiliar spender addresses as a hard stop.
- Revoke any outstanding Permit2 approvals for the affected token via the Permit2 nonce-invalidation or a revocation tool, since a compromised signing flow does not necessarily reveal other outstanding permits.
- Flag and monitor the identified attacker cluster addresses across chains, expect reuse of the same funding EOA and receiving addresses in future incidents.
- Report the phishing domain
airdrop-safe-claims.example for takedown and add it to browser/extension blocklists.