Balsn CTF 2019 - Creativity
Challenge
Creativity
is one of my two smart contract challenges for Balsn CTF 2019. You may find the source files here.
Be concise, or be creative.
- Type: Smart contract
- Solves: 1/720
- Keywords: CREATE2, CREATE2 reinitialize trick, EVM opcode
Solution
TL;DR
In this challenge, our goal is to emit the SendFlag
event. According to the CREATE2 reinitialize trick, we can deploy a contract that passes the check()
, self-destruct it, and then deploy a new contract at the same address with a 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.
Detailed Write-up
We are provided with the game contract source:
1 | 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.
- Call
check()
with the address of the deployed contract as the parameter. - Call
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
LOG1
requires 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.
The CREATE2 Trick
The 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
reinitialize 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
reinitialize trick (re-written from this contract). All contracts deployed by deploy(code)
through Deployer
will be deployed at the same address. However, the code of these contracts can be different.
1 | pragma solidity ^0.5.10; |
To solve this challenge, this is our plan:
- Using the
CREATE2
reinitialize trick, deploy a contract with content0x33ff
, which isselfdestruct(msg.sender)
. - Call
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
CREATE2
reinitialize trick, deploy a new contract at the same address that will executeemit SendFlag(0)
. - Call
execute()
in the game contract, it will then fire theSendFlag
event.
Misc
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.
Author: shw
Link: https://x9453.github.io/2020/01/04/Balsn-CTF-2019-Creativity/
License: CC BY-NC 4.0