A-MAZE-X CTF Walkthrough | Part II

matta @ theredguild.org
8 min readSep 27, 2022

--

This post belongs to a series of articles dedicated to solving the DeFi challenges hosted by Secureum at Stanford University on the past 26 of August.

Additionally, this series is part of a greater series called A journey into smart contract security.

A-MAZE X CTF
Image extracted from LiveOverflow’s YT channel

We're barely heating up! Are you ready for the second challenge?

Table of contents

1. Challenge1.lenderpool — What a nice Lender Pool!
1.1. Challenge description
2. The code
2.1. First part
2.2. Second part
2.3. Third part
2.4. Dissecting flashloan()
3. Vulnerabilities
4. The exploit
5. Solution
5.1. An easier solution

Challenge1.lenderpool — What a nice Lender Pool!

Again, before jumping to the challenge description, let’s do some thinking first.

Reminder to pause, and really think every time you see this emoji near a paragraph: 💭.

— By looking at the name of the file, and the title of this challenge, what can you conclude? — think. 💭

— That it’s going to be about lending? a lending pool?

— Great! Do you know what a lender pool is?

—Uhm, I'm guessing it's going to be some kind of contract where one could borrow tokens or ether, with all the things associated with it (fees, rates).

— Exactly, you may ask for a loan.

Can you anticipate possible attacks within this context? 💭

Let's go to the challenge description to gather more information.

Challenge description

Secureum has raised a lot of Ether and decided to buy a bunch of InSecureumTokens ($ISEC) in order to make them available to the community via flash loans. This is made possible by means of the InSecureumLenderPool contract.

📌 Upon deployment, the InSecureumToken contract mints an initial supply of 10 $ISEC to the contract deployer.

📌 The InSecureumLenderPool contract operates with $ISEC.

📌 The contract deployer transfers all of their $ISEC to the InSecureumLenderPool contract.

📌 The idea is that anyone can deposit $ISECs to enlarge the pool’s resources.

Will you be able to steal the $ISECs from the InSecureumLenderPool? 😈

The description explains how many contracts are there, and how are they interacting. Fortunately is pretty straightforward, we will see if the code is as easy as it was reading this.

New concepts have been introduced, but we will talk about them when their time comes.

Our goal is to steal all the tokens from the InSecureumLenderPool contract.

The code

I’m going to split the code into three parts to analyze them further.

First part

So here's the first chunk of code for this second challenge.

The first part of Challenge1.sol

It doesn't say much about possible attack vectors except for its domain. It is hinting to us it can be something related to tokens transfer and how this contract manages their loans. Nothing we haven't implied from before.

Second part

Deposit and withdraw functionality.

The second part of Challenge1.sol

From its looks all I can see is that it is not using OZ's safeMath to do arithmetic operations, which will revert if it sees anything funny, like under/overflows. The latest compiler versions takes that into account already.

From Solidity’s doc, v0.8.0 release.

Arithmetic operations revert on underflow and overflow. You can use unchecked { ... } to use the previous wrapping behaviour.

Checks for overflow are very common, so we made them the default to increase readability of code, even if it comes at a slight increase of gas costs.

So unless the contract code we're trying to exploit has been compiled with a Solidity version prior to 0.8.0, there will be no need to dig deeper on this matter.

Leaving aside what we just discussed, I don't seem to see anything in particular. The classic mistake here would be sending tokens before decrementing the amount, but even safeTransfer is being called.

Let's move on, and maybe come back later.

Third part

Let's suppose you have enough ether to pay for a transaction. You suddenly see an opportunity: someone is selling an NFT at 1 ether, and a collector is buying NFTs from that series at 2 ether.

— Oh, how I wish I had 1 ether right now! If only there could be a way to ask for it and return it asap, even with interest!

Let me introduce you to: 🌟 flashloans 🌟

If you don’t know the idea behind a flashloan, let me sum it up for you: you can ask for a loan, and within the same transaction you somehow have to return it.

Following the example, you could create a smart contract that upon the execution of its main functionality, it would ask for 1 ether (loan), buy the NFT, sell it, and return it (with the interest of course). It's all done in a single transaction (in a flash).

It’s a game changer! Someone with just enough eth to pay for a transaction can use a flashloan to make a financial difference. This was one of the very first things that convinced me back into the field (for real).

Now back to the code.

For obvious reasons, my intuition says the vulnerability must be in the flashloan implementation. Mainly because it is the only one providing critical functionality, and because it's the last piece of code left 😅.

Dissecting flashloan()

The code is short and straightforward. It checks the token balance of the contract before and after what appears to be a delegate call from the extended functionality on Address.

The directive using A for B; can be used to attach functions (A) as member functions to any type (B). These functions will receive the object they are called on as their first parameter (like the self variable in Python).

— But why would a delegatecall be used in this scenario? 💭

— Uhm, maybe because in that way you force msg.sender to operate with the borrower contract directly?

Delegatecall illustration with msg.sender

—That seems unnecessary... And what other consequences does this entail?

— Oh! I know I know! Isn't this something like importing the other contracts' code inside of mine?

—Cloooose enough. You may see it as using the callee's contract code within the caller's context.

— Isn't that like, bad?

— Depends on what you're trying to achieve. For a good example of this, you can read more about upgradable contracts.

— And in this case? 👀

— Well, that's on you to find out. And let me ask you a question. What would you do if you could manage the challenge contracts' storage slots? 💭

— Duh, I'd add infinite balance to my address.

—And why don't you do it then? With the borrower’s contract code you have access to InSecureumLenderPool storage slots.

— Wait what? 😵

Vulnerabilities

Whenever interacting with another contract using delegatecall, all modifications to the callee contract's storage variables, impact directly on the caller's storage accordingly to the position of the storage lot of each one of them.

If I were to create a contract, whose first declared variable is an address called "example", it will be assigned the storage slot 0. And if I somehow modify that variable, under the hood I will be modifying the data structure which is in slot 0.

Note: Representative code. Don't try to compile this.

— So if I have the contract from above, and I run a delegatecall from InSecureumLenderPool asking to run setExample with a new address, what do you think it's going to happen?

— I'm trying to picture it in my mind.

— Let me help!

Visual implication from running setExample with a delegatecall.

— Oh, since its first storage lot has been assigned to variable token, the new example address is going to overwrite token's current one!

— Great! Now back to our objective. Let's steal the moneyz 😈.

If instead of using a delegatecall the contract used a simple call, would this have happened? 💭

The exploit

There are different things that can be done over here:

  1. modifying flashloan variable status to false: we can avoid reentrancy protections!
  2. swapping the token's contract address to a fake one where the balance always returns bigger than before after returning from the delegatecall: we could trick the lender into thinking we deposited real tokens, and then withdraw the real ones.
  3. modifying balances mapping to our liking: say no more!

Solution

The approach we are going to take as a solution will involve modifying the balances mapping. We will set ourselves the maximum balance we can get.

To do this, we need to configure the exploit contract with the exact same order of the storage variables. This is mandatory to be able to work with the same storage layout.

If you pay attention, all I did was copy the first part of the target contract, and add a function named run which sets our balance to a desired amount.

Since I'm getting familiar with ethers, I decided to craft my own calldata with it.

After playing a bit with hardhat console, poking around the utils part of ethers, and reading the documentation back and forth, I ended up with this:

The basics to make a call to a function in this way, are knowing the function's selector, and all the parameters that will be appended to it.

The function selector are the first 4 bytes of the result from applying the hash function keccak256 to the function signature in need.

bytes4(keccack256("function(type1,type2…")))

I also created a fallback in case my calldata was missing something, I wasn't letting my attack go to waste 😅.

Additionally, I used an alternative I found later to create the calldata using encodeFunctionData and compared it with the one created by myself.

The resultant Javascript code to deploy and execute the attack is below.

An easier solution

Now let me ask you, did you see an easier way to achieve this challenge's objective? Something that we can reuse from previous experience? 💭

What if I tell you we could reuse what we did in our previous post?

Inside solveChallenge1.jsright after the comment that reads "Deploy Challenge Contracts", you will find the following line: const isecTokenFactory = await ethers.getContractFacgtory("InSecureumToken", deployer) . Do you know what this means?

That's right, the token which is being used by the lender pool is the same as the one we already exploited: InSecureumToken.

We could just approve ourselves as spenders in the name of the lender pool, and then transfer all the tokens to ourselves.

I leave this simple exercise to you 😄.

Journey character render flying away to the next challenge
Render from Journey character by lugalque at DeviantArt

See you in the next challenge of this series! ➡️

Thanks for reading! My name is Matt, and I’m learning how to make Ethereum more secure. I will be sharing some things from time to time.
Follow me on twitter
@mattaereal.

--

--