Trust Fund

Security Innovation Blockchain CTF Writeup (English version)

Teerasak R.
Coinmonks
Published in
3 min readJun 23, 2022

--

Hello everyone! This writeup is also available in Thai, for those who prefer; please click here.

สวัสดีครับ บทความนี้มีเวอร์ชันภาษาไทยเหมือนกันนะ สำหรับคนที่ต้องแบบภาษาไทย สามารถคลิกที่นี่

Contract’s Code

Solution

First, I suggested attempting to learn the contract’s purpose, workflow, and function. This can provide a summary of the contract and a hint as to which function you should inspect.

The goal of this challenge is to drain all ETH from the contract. As there is just a single function relating to transferring ETH that can be easily noted, withdraw().

So, you can head directly to the withdraw() function and figure how to call it without reverting. It has 2 require() statements that you must make it yield true upon calling, but how?

I would like to zoom out a bit and explain what this challenge is about. It intends to lock the deployed ETH and allows you to withdraw just 1/10 per year (allowancePerYear = msg.value.div(10);), hence, it will take you 10 years to solve this challenge! Wait a minute…, if it were so straight, this wouldn’t be a challenge, would it? So, let’s dive into how to break it!

The first one, allowancePerYear > 0, is not to worry at all, it will always be greater than zero because it will be set to the amount of ETH deployed divided by 10, which is always greater than zero.

The second, withdrewThisYear, must be false on calling, meaning that you must haven’t withdrawn this year. It will be set to true after calling withdraw(), if you’ve already called it, you can set it back to false by calling returnFund() with the same amount that you withdrawn.

And… the flaw is here! if you take a closer look at line 34–36, you’ll notice that the fund is sent to caller (msg.sender.call.value(allowancePerYear)()) before the withdrewThisYear is set to true, this pattern introduces the Reentrancy Attack. I’m not going to explain what this attack is, but if you haven’t heard of it before or are unfamiliar with it, please use the sources below as starting points to learn more about it:

The contract does external call before setting the value, it hand over the control to the callee, before the line 35 is processed. So, you can call withdraw() again while the withdrawThisYear still be false. To perform multiple calls in a transaction, a smart contract is inevitable; this cannot be done without a smart contract. The smart contract logic can be illustrated as follows:

  1. Call the withdraw() function.
  2. Implement the fallback()/receiver() function to recursively call the withdraw() function. This function will be triggered by the line 34 of each call. And, you must include a remaining balance checking to stop the recursive once all the fund has been drained.
  3. Transfer all drained ETH from your smart contract back to you (you can left it if you pleased).

Here is my implemented smart contract from the above logic.

You can compile, deploy, and run this with any method you like. You just have to input the challenge address (TrustFund.sol) on deploying to set the target.

And if you’re playing on the SecurityInnovation website, don’t forget that the challenge is inherited from CtfFramework.sol. You have to include your deployed smart contract address as the allowed caller by calling ctf_challenge_add_authorized_sender().

After everything is set, just call pwn() and done 😎.

Lesson Learned

This challenge is clearly has the Reentrancy Attack flaw, therefore, the good mitigation can be found here. And I’m gonna wrap up as follow:

  • Do not make external call before changing internal state is for the best, but if necessary, please make sure that the external callees are the trusted ones.
  • Openzeppelin’s ReentrancyGuard is very helpful and made for this. However, only use it with functions that require non-reentracy, not for all functions, as this includes more check, so, costs more gas.
  • You can also consider using Rari-Capital solmate’s ReentrancyGuard for more gas optimized than the Openzeppelin if you are planning to use one.

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

Teerasak R.
Coinmonks

Just publish for fun on interests, not a full-time writer. I’m happy if you love my stories.