Anatomy of a Baiting Attack on MEV Arbitrage Bots: Lessons Learned from $177k Robberies 💰
Beware of what's hiding in the Dark Forest
The attacker has launched 11 attacks targeting MEV arbitrage bots. Of these, eight have incurred losses totaling $177,253.
The first attack took place on Apr-21-2023, and the attacker is still actively luring new victims.
The attacker sets up token contracts with a harmful
transfer()function and entices the arbitrage bot to trade for the token. Having already deciphered the bot's input data, the attacker waits for the bot to take the bait. Once the bot does and the harmful transfer initiates, the attacker manipulates the
uniswapV3SwapCallback() with doctored data parameters. These parameters pass the contract's checking settings and deceive the system into rerouting the transfer to the attacker's EOA address instead of the legitimate one. Thus, the attacker manages the transfer and swap process according to their preferred parameters and successfully siphons off all assets from the bot.
Typically, the victims are immature bot contracts that lack caution in designing callback functions. This oversight makes it easier for attackers to decode and forge parameters, allowing them to swiftly clear out the bot contract's balance.
The attacker meticulously designed a remote switch contract to bypass the bot's local simulation execution check. They only flipped the switch to attack mode when the chain had already packaged the bait and arbitrage transactions.
The attackers mainly bear the cost of constructing contracts to serve different purposes and paying priority transaction fees to ensure the execution order of transactions. These expenses total approximately 5.49 ETH. The payoff, however, is substantial, yielding a return on investment of 1800%.
It’s a great day to do some arbitrages. And your monitor detects a signal transaction. Pressing the button, you are happy that this week’s grocery is already paid for. Yet one minute later. You start to wonder: hold on, where are the balances of the arbitrage bot?
Congratulations, you’ve become a victim of this MEV-baiting attacker.
In early April, a malicious validator enticed sandwich bots into considerable losses, creating a buzz in the MEV dark forest and sparking concern among MEV bots. This article presents a different type of attack, specifically aimed at luring and trapping MEV arbitrage bots. In this scenario, the attacker manages to transfer the entire balance from the bot contract directly into their own pocket. Examining these baiting attacks and identifying key preventive strategies play a crucial role in maintaining the safety and security of the MEV field.
Unravel the Attack
Our investigation uncovered an arbitrage bot that had fallen into a trap while attempting to transfer tokens between pools on Uniswap V2 and Uniswap V3. On June 19th, at block height 17511729, the bot encountered a trap contract named token SUS, filled with malicious code. When the bot spotted an arbitrage opportunity involving this contract, it initiated a transaction in its usual manner, expecting a straightforward arbitrage process, as shown in the subsequent figure.
However, during this attack, the bot completed the arbitrage process with the provided parameters and earned a revenue of 0.0085 ETH, but it also activated the malicious
transfer() function within the SUS contract. This function carried out additional operations, letting the attacker exploit the bot contract's encoding format and call its callback function using forged parameters.
transfer() function even manipulated the gasUsed when it turned off the attack mode, resulting in a higher gasUsed budget (80143 gas) compared to the actual attack (70838 gas). This trick ensured that the transaction would pass the tx.gasLimit check and avoid being reversed. Note: a standard ERC20 token transfer costs 21000 gas when the recipient doesn't own the token and 13000 gas when the recipient already possessed the token before the transfer.
This manipulation led to two transfers of the balance from the bot's contract to the attacker's address, totaling 1.5852 WETH and 827.7170 USDT, as illustrated in steps 2 and 3 of the below screenshot for the attacker's transaction.
But how exactly did it happen? To understand that, we need to go into the code: the CallTrace Tree.
Attack Principle and Conditions
From the CallTrace Tree of this transaction, we can further analyze the entire attack process and principle.
The call traces within the green box in the above figure correspond to the attack steps the attacker constructed in the SUS contract's
transfer() function. When the arbitrage bot calls the Uniswap V3: SUS contract's
swap() function to Flash Swap WETH and SUS, Uniswap V3: SUS will first transfer the specified amount of WETH to the bot contract 0x9ee, check the balance of SUS, and call back the bot contract to execute the specified operation within a customized callback function. Here, the callback function defined by 0x9ee is named
uniswapV3SwapCallback(amount0Delta, amount1Delta, data), with three parameters. The primary purpose of the
data field is to pass the bot-encoded transaction instructions to the callback function and execute them according to the transaction instructions when the function is called by a third party.
When Uniswap V3: SUS calls
uniswapV3SwapCallback(), the input parameters are defined by the bot, and it will execute two steps:
Transfer: transfer the specified amount of the specified token from the bot address 0x9ee to the specified address.
Swap: call the swap method of the specified contract and exchange the specified amount.
So far, everything has been going smoothly, but during the first callback process, the Uniswap V2: SUS 19's
swap() function was triggered, which further triggered the process of the SUS contract transferring SUS tokens to the Uniswap V3: SUS contract. This process caused the execution of the attack steps within the green box.
Execution of the Attack
During the attack, the attacker began by contacting a contract at address 0x754 to verify if the attack conditions were met. Next, the attacker reached out to the contract at address 0x636 to execute two repeating steps in different modes, shifting WETH and USDT from the bot's address to the attacker's specified address.
In carrying out these steps, the attacker first checked the balance of the bot contract, got the amount he was allowed to transfer, and then called the
uniswapV3SwapCallback(), passing in forged data parameters that passed the contract's checking settings, thereby executing the transfer and swap process based on the attacker's specified parameters. The attacker falsified the receiving address for the transfer to its own EOA address 0x613, and since there were no strict checking settings for the swap method in the callback function, even if the parameters passed in did not result in a valid execution, this did not trigger a transaction reversion.
To carry out these steps, the attacker started by checking the bot contract's balance and determining the amount they could transfer. Then, the attacker called the
uniswapV3SwapCallback(), inputting forged data parameters that met the contract's checking settings. This action allowed the execution of the transfer and swap process based on the attacker's chosen parameters. The attacker rerouted the transfer to their own EOA address 0x613, by falsifying the receiving address. The callback function didn't have rigorous checking settings for the swap method, so even if the input parameters didn't lead to a valid execution, it didn't cause a transaction reversal.
Decode the Parameter
When the attacker calls
uniswapV3SwapCallback(), they forge the input parameters. To do this, the attacker must sift through the bot's transaction history and identify the encoding pattern for parameters. This allows them to reverse-engineer the meaning of the "data" field in the callback function. The figure below presents the data sent to the callback function during three invocations in the current transaction, along with their corresponding interpretations.
We speculate that 0x4980 and 0x4990 relate to the callback function's operation instructions and token information. By decoding the remaining string, we can easily identify the corresponding addresses and the decimal values of the hexadecimal numbers. These values match the actual transfers during the transaction. It's evident that if the callback function's encoding is in plaintext or a simple form, it can be decoded with ease.
This manipulation gave the attacker control over the bot's output and the ability to insert their own instructions. Consequently, the attacker could execute trades beyond the bot user's set parameters, generating a substantial profit for themselves and inflicting losses on the targeted users.
Attack Preparation and Conditions
Next, let's review the attacker's preparation and conditions for the attack. In this attack, the addresses related to the attacker are listed in the table below:
On May 28th, the attacker created the SUS token contract and liquidity pools for SUS/WETH on both Uniswap V2 and V3. After this, they made a 0x754 contract, which also played a role in this attack.
Then, on June 19th, just three minutes before the attack, the attacker set up the executing contract 0x636, mainly used to carry out the attack. The input data provided for the transaction that spawned this contract suggests that it was purposefully created to target the bot contract 0x9ee.
Another crucial step is baiting the target bot by sending a dummy transaction. By instigating a swap in a pre-created pool, the attacker generates an arbitrage signal, luring the bot into initiating related arbitrage transactions. In this scenario, we observed that the bait transaction occurred just before the attack transaction.
The attacker also initiated the first transaction in the current block, serving as an attack switch, as indicated by the "Set checker" method parameter. This switch controls the activation of the attack mode in the malicious token contract remotely.
It's important to note that arbitrage bots often simulate execution locally before sending the bundle to predict potential profits. To keep these bots from detecting losses during the simulation phase and avoiding the bait, the attacker can send bait transactions that have not activated the attack mode to the mempool. This bypasses the bot's simulation phase inspection. Then, by paying a higher priority fee or builder tip, the attacker ensures the transaction that remotely activates the attack mode is placed before the victim's transaction. As a result, the attack steps are executed when the builder officially processes these transactions.
In the call trace tree of the attack transaction, we can see a step that calls this switch contract to check the attack conditions.
How to Fortify Your Bot Against Such Attacks?
To defend against this type of attack, the bot could implement several strategies. One method is to add
tx.origin = owner in the callback function and ensure that all externally callable functions check for balance changes. While these checkpoints provide some protection, they also result in an increase in gas costs. Additionally, trading bots should be aware of the hidden risks of callback functions and set strict condition limits within the callback, if necessary. While this may limit the contract's flexibility, it can enhance its security.
However, callback functions have always been a vulnerability in Ethereum smart contracts, making it challenging to avoid other creative attack strategies exploiting it for gain.
Deep Dive into Attacker's Historical Patterns of Deception and Exploitation
Aside from the case previously analyzed, there are additional victims who have suffered losses from the same attacker. After analyzing the attack behavior, we have identified more victims whose bot assets have been drained by the attacker for the same reason. All the attacks follow a similar pattern:
The attacker creates a malicious token contract with a malicious transfer() function.
Create pools in Uniswap V2 and Uniswap v3 and add liquidity to the pools for users to trade the malicious token.
The attacker submits a luring swap transaction, emitting a bait signal to lure the arbitrage bot into trading the malicious token within the pool.
In order to bypass the bot's security checks when they simulate the transaction locally, the attacker submits a transaction that acts as a switch-on for active attack mode just before the victim's transaction by paying a higher priority fee or builder tip. Once the victim's transaction is executed, the attack is launched, following the steps outlined in the 'Attack Principle.'
List of Past Attacks
The chart below is ordered based on the time of each attack, starting from the earliest to the most recent. The malicious tokens that the attacker has utilized to deceive and tempt the victims, along with the contract addresses of the victims who have suffered losses from the attack. If you’re in how the victim's money has been transferred out, detailed information, including token flow charts about victims' transactions, is provided in this spreadsheet.
The Victim Suffered the Most
This attacker has initiated 11 attacks, where 8 victims have suffered loss from the attack. Some victims have been attacked twice since they didn't realize the attack in time. The attacker managed to steal assets worth $177,253 from victims’ losses. Additionally, the most prominent victim suffered a loss of $64,047 in a single transaction. As shown in the below screenshot, step 2-5 are the unanticipated transfer executed by the attacker, where all the asset in the bot has been stolen. Ironically, the attacker deceived this victim through a token named 'UNSAFE,' which has been proven to be quite unsafe.
The Attacker Being Sandwich Attacked
In order to lure the bot into executing the desired back-run transaction, the attacker often needs to submit a swap transaction in the pool they created. Interestingly, we have observed that the luring transaction initiated by the attacker has become a victim of another sandwich attack. The dark forest is indeed bustling, with the hunter becoming hunted.
Keep An Eye on These Addresses
The table below shows the address group associated with the attacker, which the attacker actively used to carry out the attack. This includes:
The EOA address the attacker uses to gather stolen assets
The contract addresses the attacker uses to submit switcher transactions
The EOA and contract addresses the attacker uses to submit luring transactions
For each attack, the bot creates an attack executor contract. The targeted bot’s address is saved in the input data of the switcher transaction.
The attacker's total cost includes the transaction fees for the luring and switcher transactions, as well as fees for contract creation, pool creation, and adding/removing liquidity transactions, among others. The attacker must also pay higher priority fees and builder payments to ensure their switcher transaction takes precedence over the victim's transaction. The attacker submitted a total of 642 transactions to set up the attack and lure the victim at a total cost of 5.49 ETH, or around $9,497. However, the payoff is substantial, with an ROI of 1800%.
Be Aware, Be Prepared
We've uncovered an attacker who specifically targets arbitrage bots, stealing all the assets from their bot addresses. Besides the attack on MEV sandwich bots that we mentioned earlier, we've observed a variety of different attacks. Some attackers exploit contract weaknesses, while others manipulate the market or use new techniques that result in risky events. Our analysis of this attacker aims to raise awareness among trading bots about the importance of taking security issues into account during function design. It also emphasizes the need to establish strong security checks to ward off malicious attacks. We designed our transaction analysis tool, EigenTx, specifically to increase transparency in blockchain transactions. It aims to help designers identify potential risks and support the development of sturdy and secure DeFi trading systems.
Follow us via these to dig more hidden wisdom of DeFi: