Skip to main content
Sigvex

Arbitrary Storage Write Exploit Generator

Sigvex exploit generator that validates arbitrary storage write vulnerabilities by simulating user-controlled SSTORE operations targeting critical storage slots including the owner address.

Arbitrary Storage Write Exploit Generator

Overview

The arbitrary storage write exploit generator validates findings from the arbitrary_storage_write, uncontrolled_storage, and related detectors by simulating the attack where a user-controlled slot parameter in an assembly SSTORE operation is used to overwrite slot 0 (the owner address). If the simulated storage write succeeds in changing the owner to the attacker’s address, complete contract takeover is confirmed.

Unchecked assembly SSTORE with user-controlled slot arguments allows attackers to write any value to any storage slot. Since slot 0 is conventionally the owner address in simple contracts, this typically results in complete ownership takeover — after which the attacker can call any onlyOwner function to drain funds or destroy the contract.

Note: Exploit generation in Sigvex is for vulnerability validation purposes only.

Attack Scenario

Direct slot overwrite:

  1. A contract exposes setStorageAt(uint256 slot, uint256 value) implemented as assembly sstore(slot, value).
  2. No access control or slot validation is present.
  3. The attacker calls setStorageAt(0, uint256(attackerAddress)).
  4. Slot 0 (the owner variable) is now the attacker’s address.
  5. The attacker calls withdraw() (protected by onlyOwner) to drain all contract ETH.

Array index overflow (indirect):

  1. A dynamic array is stored at slot 3. Its elements are at keccak256(3) + index.
  2. A function writes to array[index] via assembly without bounds checking.
  3. The attacker computes offset = type(uint256).max - keccak256(3) + 1.
  4. Writing to array[offset] computes keccak256(3) + offset ≡ 0 (mod 2^256), targeting slot 0.
  5. The owner slot is overwritten.

Exploit Mechanics

The generator simulates the attack by comparing storage state before and after the exploit transaction. It configures slot 0 with a legitimate owner address, executes the attacker-controlled storage write operation, then reads slot 0 back from the post-execution state.

If the owner slot value changed between the before and after snapshots, the vulnerability is confirmed. Evidence collected:

Field Value
vulnerable_slot 0
original_owner Legitimate owner address
new_owner Attacker address
attack_type Arbitrary storage write - owner takeover

Estimated gas: 50,000. The six-step exploit sequence demonstrates the complete attack path from slot analysis through fund extraction.

The generated PoC shows both attack vectors:

// VULNERABLE: User controls which slot to write
contract VulnerableStorage {
    address public owner; // Slot 0

    function setStorageAt(uint256 slot, uint256 value) public {
        assembly {
            sstore(slot, value) // No validation!
        }
    }
}

// EXPLOIT: Overwrite owner slot directly
contract ArbitraryStorageWriteExploit {
    function attack1_directSlotOverwrite() public {
        // Slot 0 = owner address
        target.setStorageAt(0, uint256(uint160(attacker)));
        // target.owner() == attacker now
    }

    function attack3_drainFunds() public {
        // Now we can call onlyOwner functions
        target.withdraw(); // Drain all ETH
    }
}

Remediation

Eliminate user-controlled assembly storage operations. Use Solidity’s high-level storage abstractions exclusively:

// VULNERABLE: User controls storage slot
function setStorageAt(uint256 slot, uint256 value) public {
    assembly { sstore(slot, value) } // Never do this
}

// SECURE: Use Solidity mappings (slot calculation is safe)
mapping(uint256 => uint256) private userData;

function setUserData(uint256 key, uint256 value) public {
    userData[key] = value; // Cannot reach owner slot through mapping
}

// SECURE: Bounds-checked with access control
function setDataSafely(uint256 index, uint256 value) public onlyOwner {
    require(index < 1000, "Index too large");
    userData[index] = value;
}

When assembly is unavoidable (e.g., proxy implementations), implement a slot allowlist:

  • Define permitted storage slots at compile time
  • Reject any sstore target that is not in the allowlist
  • Use the ERC-1967 standard slots for proxy storage to avoid collisions with implementation variables

References