Hey bot! Give me all your money…

Tomasz Mierzwa
Coinmonks
8 min readOct 1, 2020

--

TL;DR: Traders that have a bot that ever traded on Uniswap can claim 400 UNI for it. But they need to be careful! Somebody can rob them if the bot is not properly protected. This is a story of such an exploit by a hacker for whom it was just a warm-up before exploiting $EMN, and a cautionary tale for reckless bot operators.

We were inspired by the case nicely described by @FrankResearcher in his recent tweet:

Is it really possible to claim UNI for an arbitrary contract that traded on Uniswap and then steal tokens from it?

After a bit of data wrangling with @bkiepuszewski and a few coffees we realised that in many cases it is possible and this exploit can easily be used to steal any tokens from a bot’s inventory.

So check your bots before it’s too late!

Generic arbitrage schema

When the price of the same asset on two exchanges is different, it creates an opportunity to instantly buy in one place and sell in another.

For example if 1 UNI = 0.01219 ETH on Exchange1 and 1 UNI = 0.01321ETH on Exchange2 and you happened to have 10 ETH you can:

  1. Trade 10 ETH for 820.3445 UNI on Exchange1
  2. Immediately trade 820.3445 UNI for 10.8367 ETH on Exchange2
  3. Enjoy your profit which is 0.8357 ETH minus gas fees

There are hundreds of bots looking for such opportunities on every market and it is typically a win-win situation. Bots generate profits for their operators, but at the same time they keep markets in balance (the price of an asset tends to level in time on every exchange).

Simple arbitrage bot architecture

The catch here is timing. Arbitrage traders have to spot the opportunity before others do and act quickly, because their success depends on two separate trades. The best solution for them is typically to create an arbitrage bot.

There are many possible architectures and I’m not going much into technical details, but typically it will consist of:

  1. Off-chain logic — the ability to track market prices and gas costs in real time, to find arbitrage opportunities, to assess risk and the risk/reward trade-off, etc.
  2. Operator — the Ethereum address used to trigger the bot’s action.
  3. Bot itself — a smart contract, having: inventory (it needs funds to initiate the arbitrage), functions (to wake it up and give it commands), parametrisation (to tell the bot what to do).

Arbitrage example

Let’s look at the bot history before it got exploited.

For example in this transaction it earned 0.0197 ETH on arbitrage between Balancer and Uniswap on WETH/MCX market, spending 0.0067 ETH in gas fees.

One can imagine the dialog between the bot and its Operator:

The Operator spots that the price of MCX on Balancer is a bit lower than on Uniswap.

Operator — “Hey bot, wake up. This is 0.65 ETH. Go and buy some MCX on Balancer and immediately sell it on Uniswap.”

Bot — “Yes, yes, yes! Let’s make a PROFIT!”

Bot trades 0.65 ETH on Balancer and gets 9955.29 MCX.

Bot — “Hurray, the trade was successful. Let’s check my pockets. Wow I have 9955.29 MCX, let’s make a PROFIT!”

Bot trades 9955.29 MCX on Uniswap and gets 0.6696 ETH.

Bot — “Hurray, the trade also was successful. I have 0.6696 ETH, made from an initial 0.65 ETH. That’s a good PROFIT! I’m such a great bot. Hug me please…”

Vulnerability

To make it all happen there are at least two commands that need to be understood by the bot.

  • Some kind of “do_arbitrage” command where the Operator says where, what and how the assets should be traded; and
  • Some kind of “give_me_your_money” command where the Operator withdraws earned profits from the bot’s pockets.

It’s very obvious that the second command must be as protected as possible, because the Operator definitely does not want others to steal its precious tokens.

But the first one seems to be quite safe. If the bot is smart enough to check if arbitrage was successful and profit was made, then why should the Operator protect this? Maybe it’s good to even encourage everybody to earn money for us by calling the bot?

This was a possible reasoning behind the Operator leaving the “do_arbitrage” function unprotected in the bot that got exploited.

The second vulnerability was to give too much freedom in parametrising the calls. Actually the bot assumed that at least one trade should be made with UniswapRouter using the swapExactTokensForTokens function, but this was not a big problem for a smart exploiter.

Claiming UNI for a contract

Now a short digression about claiming UNI tokens.

As announced by Uniswap (link), every individual Ethereum address which traded on Uniswap2 is eligible to claim 400 UNI. This also means that contracts (especially bots) that ever traded on Uniswap2 can claim their bounty.

But such a bot that has already been deployed, probably does not have a “claim” function implemented, so you may think that nothing can be done.

But Uniswap provided another method — you can claim UNI on behalf of another address.

This is an example of such transaction using EthTx.info.

So the contract gets rewarded with nice new UNI tokens but the real problem is how to withdraw them from the bot’s pockets? It might be simple if it is your contract (and you are authorised to call its functions). But what if for some reason you cannot do this?

Or what if it is not your bot?

This is where this story becomes interesting

How this happened

An attacker found a vulnerable bot and claimed 400 UNI on its behalf (transaction).

So now the bot has the UNI tokens, but the only way to extract it is by using the “transferERC20(address _to, uint256 _value)” function which includes a simple and powerful guard:

require caller == 0x2d033fe0afc028a71f54536d3ec8ec08e60300d4

Which means that if you are not 0x2d033… you cannot do anything here.

But wait…, there was still the ‘do_arbitrage’ function that anybody could call. As shown in the public transaction data, the only thing an exploiter needed to do was to prepare fake exchanges to trick the bot.

This is what happened. The attacker deployed two contracts pretending to be exchanges and asked the bot to do an arbitrage between them.

  • The first exchange was the simplest possible one — it’s functionality was… to do nothing;
  • The second exchange was a bit more sophisticated — it pretended to be UniswapRouter and its fake swapExactTokensForTokens function simply transferred the tokens to the exploiter.

So let’s imagine the dialog this time around:

Exploiter claims 400 UNI for the bot and deploys fake exchanges.

Exploiter — “Hey bot, wake up. This is 0.00001 ETH. Go and buy some UNI on Exchange1 and immediately sell it on Exchange2.

Bot — “Yes, yes, yes, let’s make a PROFIT!”

Bot thinks it traded 0.00001 ETH on Exchange1, but in fact nothing happens (“nothing” also means that the trade did not revert). This is the mysterious empty call in the transaction trace here:

But the bot is only a bot.

Bot — “Hurray, the trade was successful. Let’s check my pockets. Wow I have 400 UNI, let’s make a PROFIT!”

Bot trades 400 UNI on Exchange2 and gets nothing. Behind the scenes the fake Exchange2 transfers the UNI tokens to the exploiter’s address.

Bot — “Hurray, the trade also was successful. I’m not checking the PROFIT because my dearest Operator said that minProfit is zero so I do not have to. I’m such a great bot. Hug me please…”

Here is the actual exploiting transaction (more details below).

Lessons learned

The exploiter claimed 400 UNI for somebody else’s bot. Then they tricked it to withdraw the UNI by using a publicly accessible function and deploying two fake exchanges.

What can be learnt from this:

UNI can be claimed for a bot if it ever traded on Uniswap, the only question is how to withdraw it to the Operator’s wallet (but if it is your bot, you may already have a functionality implemented for this);

Potentially other bots might be vulnerable to this exploit. It requires a public and unprotected “do_arbitrage” function and some levels of flexibility with parametrising the trades. All Operators should check their bots to ensure that they are not susceptible to this.

Any tokens from a bot’s inventory can be stolen with this technique. It is not restricted in any way to UNI. If a bot has anything in his pockets it could be tricked to send it to a third party address.

Be aware, and take appropriate precautions!

And think for a moment because you may have forgotten that your bot can easily give you 400 UNI for a nice new PlayStation.

Or that it could give it to anybody if it the bot is not properly protected.

Going deeper

Bartek Kiepuszewski reverse engineered the bot’s code:

This pseudocode shows the exact functionality of this very simple but also efficient creature.

Using EthTx.info the trace of the exploit transaction looks like this:

In a single transaction, the exploiter deployed two fake exchanges, triggered the bot and withdrew the UNI.

A precisely executed exploit just before draining millions of Dai from $EMN.

Disclaimer: The information and research provided in this article is for informational and educational purpose only. Any use of any of the information provided for any purpose is at your own risk.

As always many thanks to @bkiepuszewski for his inspiration and help :)

Also, Read

--

--

Tomasz Mierzwa
Coinmonks

Chief Data Wizard @ Token Flow, EthTx.info creator, DeFi enthusiast and private pilot.