In a recent tweet that gained some popularity, we described the curious tale of Jhon Doe [sic]. Jhon went to sleep with a false sense of security after withdrawing all their funds from a questionable farming scheme believing no harm could be done as long as the funds were in their wallet.
Much to their surprise, Jhon woke up to discover half of their $UNI tokens were removed from the wallet, without them ever authorizing or signing a transaction.
Jhon’s private keys were never compromised, and there was no bug in the wallet. What made this hack possible is a known but commonly overlooked flaw in the design of the ERC20 standard used by most popular tokens on the Ethereum network.
If you would like to read a short primer on what makes this hack possible, read our original post on baDAPProve. In that post, we predicted how the increasing popularity of DeFi Dapps, coupled with an insufficient warning regarding this issue, might lead to loss of funds. This scam is the first time we have seen it publicly exploited. If you want to go down the rabbit hole and discover the true extent of this hack, keep reading.
Phase 1: Cats go Phishing
UniCats was launched as another spin-off from popular DeFi projects such as Sushiswap. Unsurprisingly, they even used the exact same frontend of Sushiswap, because why bother (the official website is down, but an archive view is available).
Users who found their way to UniCats were promised $MEOW tokens if they staked either $UNI or $UNI LP tokens on UniCats. The choice to go with $UNI staking is no coincidence. Many users got a bunch of $UNI for free and thus might have felt less concerned with risking their newly found fortunes.
Jhon Doe was one of the users who decided to participate in the protocol. With the current atmosphere of “audits are for noobs” and FOMOing into any new farming scheme, it’s hard to blame them.
Jhon deposits 17K $UNI (~$50K) and another 15K $UNI (~$45K) to the UniCats contract. This, of course, required Jhon to first approve unlimited access of the UniCat contract to their $UNI tokens. Jhon probably saw the generic approve message that everyone gets when first engaging with a new contract, and did not give it much thought.
At first, everything was okay. Jhon started farming and making some $MEOW. After a day of farming, Jhon actually removed(!) all their $UNI from the Unicats contract ( 1),( 2). So far, no harm, no foul. Knowing when to quit is a great virtue. Jhon must have felt really great, going to sleep knowing they have made some gains, and funds are SAFU from any potential rug pull.
These actions were even luckier than one might think. A short time after Jhon removed the funds, the UniCats admin, let’s call them Whiskers, started pulling liquidity out of the contract, draining all the $UNI and $UNI LP tokens deposited by unsuspecting users.
Other users were not as fortunate as Jhon. They found a dried-up pool when they figured out it was all a scam and tried to remove their funds.
Well, nothing surprising so far. A shady project pulls the rug, the main dev says “there was a bug” “that the project got “hacked,” that they “worked really hard” and are now “taking their rewards.” There was also the usual line that states that they are hoping for a successful future for the project and are now “leaving this wonderful project to the community”… Right before deleting the account and disappearing with the gains. You know… the usual.
Jhon was fortunate to avoid this painful loss, and right at the nick of time. Little did they know that the story was not over, and the approved transaction would come back to haunt them.
About a week passes, and the former members of UniCats, come to accept their fate. But then, Whiskers decides to take the scam one step further.
Phase 2: Cats get greedy
The staking contract of UniCats has an interesting function, un-alarmingly called
setGovernance. Someone casually reading the unaudited contracts (that were made public) might have skimmed over this part, as it’s quite popular to have an admin key and governance address in smart contracts (but that’s another story).
setGovernance function is what Whiskers used to fetch funds right from the user’s account. The function receives two parameters, an address and some data. All Whiskers needed to do is set
_governance to the UNI token address, and for the data, pass the function:
transferFrom(from: Jhon Doe, to: UniCats, amount: 10,000).
This initiates a call to the UNI contract, requesting it to transfer funds on behalf of Jhon to the UniCats contract.
The caller of
transferFrom is, in this case, the UniCats contract, which, conveniently, Jhon has approved to use all their $UNI at their will. The same
trasnferFrom which was used to move Jhon’s $UNI into the contract for the purpose of staking, is now used to steal funds from their account.
When the UNI contract receives this call, it encounters no error trying to possess the request. All the checks pass, as Jhon had indeed approved the contract to handle their funds. The funds are pulled from Jhon’s accounts into the UniCats contract, and from there, into Whisker’s paws.
The key here is that the origin of the call in the UniCats contract that Jhon approved. Whiskers, through UniCats, could take all of Jhon’s $UNI, without any intervention from Jhon’s side.
The important message here is that infinite approval does not disappear once you have 0 tokens in your balance. Accounts with infinite approval remain vulnerable as long as the approval has not been revoked, or the account is completely abandoned.
In fact, another user of the contract, who also lost 10K $UNI (~$30K) through the exact same process, not only had their funds removed from UniCats, but also completely removed from their account, which means there is nothing to take. Unfortunately for them, they moved some $UNI back into the account, right before they were fished out by Whiskers.
To cover their tracks, Whiskers went a step further and immediately swapped the fetched $UNI for $ETH, in the same extraction transaction. The $ETH was moved to an account controlled by Whiskers and later deposited into Tornado Cash (a mixing service), in batches of 100 ETH each. This makes it very difficult to track down the Whisker’s true identity. To take it step further, and make it slightly more difficult to track them down, Whiskers performs the hack for several separate accounts. All also funded through Tornado Cash. Only the admin account of UniCats could make the call, so conveniently, Whiskers passes the ownership between the accounts, before executing the hack on the next set of victims.
Although Whiskers made it slightly more difficult to follow the tracks, there’s a lead.
Cats practice phishing, and leave some traces
By directly looking at the data filed of the contract calls made to UniCats (via Dune Analytics), we see that before executing the second phase of the hack, Whiskers actually practiced the execution of said hack on-chain. To achieve this, Whiskers created a separate contract and admin account (funded from Torando.cash) to ensure the hack works.
In this transaction, Whiskers tries to pull out a small amount of $UNI directly from the contract, using the same
setGovernance call used for the hack, fetching 2.75 $UNI ($9). This is analogous to the first phase, where the funds from UniCats contract itself were drained.
Later, with the same account performs another practice tx, which abuses the baDAPProve vulnerability, fetching small sums of $UNI from accounts funded from Tornado cash, and directly converted to $ETH. Just like in phase 2 of the hack.
These practice runs took place several hours before executing the second phase “live” on real victims. Although there is no direct link between the practicing address and Whisker’s address, both practice runs follow the same pattern and execute precisely the same hacks, so it is very likely the belong to the same operator.
Following the trace of the practicing address leads here. The account appears to be funded from Binance, so it might still be possible to trace the perpetrator through them.
While draining the UniComp contract directly, whiskers were able to fetch 17K $UNI (~$50K) as well as staked LP tokens for the $UNI/$ETH on Uniswap, SushiSwap, and Balancer. See the full list of transactions here.
In the second phase, using the baDAPProve exploit, Whiskers fished a total of 60K $UNI (worth ~$180K). These were taken out of ~30 accounts, with the greatest loser being Jhon Doe, losing a total of 37K $UNI, worth approximately $120K at the time. That’s right. More was taken from their account than they have originally staked in the protocol, as not all of Jhon’s $UNI were deposited, but were still available in their account to be fetched by the exploit.
Every account that has ever interacted with Unicats, and has not yet disapproved UniCats to use their tokens, remain vulnerable until they do so. Even those who have only approved the token but never actually sent any funds.
The Jhon Doe in this story was a heavy roller and an experienced DeFi player. Their address has over 1600 transactions, dating back a year. Jhon Doe probably knew what they were doing, and indeed managed to withdraw their funds on time before the rug pull. However, these systems are complex and are getting more complex over time. A long-lasting record is not a guarantee of security.
This case is a result of a perfect storm. A standard that requires approvals, that few understand the true meaning of. A FOMO environment, where users try to be the first to degen into risky contracts in search of enormous gains, searching for the next YFI. Users who become complicit with just clicking “next” on the frontend, and finally, a hacker who took advantage of these factors and the complexity that these systems often try to hide.
With how easy it is to make a user perform an infinite approve of a token, I would not be surprised if these kinds of exploits start popping up like mushrooms after the rain. If you would like to know first hand how it feels to get a rug pull out from under you, try our (testnet) demo demonstrating the exploit.
Appendix: How to protect yourself
The origin of the issue is with how ERC20 works and the limitations it imposes. Although other standards exist that do not have the same problem, ERC20 is still by far the most popular token standard, and is not likely to go away soon. Understanding how to work with it might just save some money.
For now, here are some best practices for future interactions with shady Dapps and smart contrats.
- Do your own research when first interacting with an unknown Dapp. Not all of them are safe. Even without baDAPProve, some users have lost their funds by staking them on the contract.
- Do not approve more tokens that you would like to risk. Wallets such as MetaMask have an option to edit the approved amount. When dealing with an untrusted Dapp, consider revising the approved filed and only allowing the amount you would like to risk.
- To retroactively revoke an approved Dapp from access to tokens, consider using one of the following tools:
Since neither Dapps nor ERC20 are going away any time soon, the best course of action is to learn and understand how these financial systems work.