- Type: Smart contract
- Solves: 1/720
Be concise, or be creative.
In this challenge, our goal is to emit the
SendFlag event. According to the CREATE2 reinit trick, we can deploy a contract that passes the
check(), self-destruct it, and then deploy a new contract at the same address with different code. Let our new contract emit the
SendFlag event, which will be executed by the delegatecall from the game contract when
execute() is called.
We are provided with the game contract source:
pragma solidity ^0.5.10;
Unfortunately, we don’t have that much ether to call
sendFlag directly, so let’s first try to follow the logic of the game contract:
- Deploy a contract with its code size not more than 4 bytes.
check()with the address of the deployed contract as the parameter.
execute()to let the game contract make a delegatecall to our deployed contract.
The biggest problem is: How can we emit the
SendFlag event by executing at most 4 bytes of EVM bytecode? The short answer is no, we can’t. Or at least in the Constantinople hard-fork, the latest release of Ethereum when the CTF is held in Oct 2019.
In short, the reasons are:
- Directly emit an event with
LOG1requires an event topic hash as a parameter, which is more than 4 bytes. (Ref)
- To invoke any type of call to another contract, at least 6 parameters should be prepared on the stack, which requires at least 6 operations and thus 6 bytes of code. (Ref)
- Modifying the storage of the game contract is useless since there is a selfdestruct right after the delegatecall.
CREATE2 opcode proposed in EIP-1014 behaves identically the same as
CREATE, except the calculated address for the deployed contract. This discussion thread points out a critical security issue of
CREATE2, the so-called
CREATE2 reinit trick, which allows a contract to change in-place after being deployed. You may find a detailed explanation in the link above.
Here is a simple PoC of the
CREATE2 reinit trick (re-written from this contract). All contracts deployed by
Deployer will be deployed at the same address. However, the code of these contracts can be different.
pragma solidity ^0.5.10;
To solve this challenge, this is our plan:
- Using the
CREATE2reinit trick, deploy a contract with content
0x33ff, which is
check()in the game contract to let our deployed contract pass the check.
- Send an empty transaction to our contract to make it self-destructed.
- Again, using the
CREATE2reinit trick, deploy a new contract at the same address that will execute
execute()in the game contract, it will then fire the
Congrats to @Sissel for being the only person who solved my two challenges during the CTF! I hope all of you enjoy this challenge and Balsn CTF.
License: CC BY-NC 4.0