Wintermute’s $162M loss in hindsight, part II.
November 13th, 2022

Wintermute was hacked for $162M on 20. September 2022. It has been one of the biggest amounts stolen from a single entity, as protocol or bridge hacks are typically spread over multiple users. This hack is extraordinary due to the concurrence of two seemingly unrelated events which only together made the exploit possible. In this piece, I follow up on the previous article, and I’m covering the on-chain events of exploit.


On-chain vulnerability

We already know how the attacker probably got the private key and therefore also power over the address. But on the day of the exploit, the compromised address was worth just a few bucks. So how did he know, that compromising this specific address would bring him millions of dollars?

Power of knowledge

Of course, I don’t have clue which source of information the attacker used. Anyway, with help of tools like Dune.com and some SQL skills you can ask and answer questions like these:

  • Which vanity addresses have sent/received funds recently, sorted by moved funds?

  • Which vanity addresses have operated with funds recently, sorted by moved funds?

The “operated” word in the second question is quite important because from my observations many of those vanity addresses are typically calling some on-chain contract, which is the actual holder of the funds. The transaction is sent from a vanity address, but the movement of those funds is happening on the contract address. Theoretically, those addresses are somehow managing the funds, but they alone are poor.

That’s exactly what applies to the compromised 0x0000000fe6a… (control wallet), which was frequently invoking the contract 0x00000000ae34… (managing contract/market-making contract). By the way, there exist many of those control vanity address wallets connected to this specific market-making contract.

Managing contract

So what is the scope of this contract, and what functions does it have? From previous experience, I would straightly look on etherscan.io and try to browse the code, but unfortunately, this contract is not verified. Also without any prior knowledge of Wintermute’s activities, I would hardly guess.

But luckily the contract was used very frequently and in such cases, there are also other approaches, which can give at least a clue about the contract’s objective. I will start with the already mentioned Dune.com and look at the compositions of used contract functions and wallets calling those functions.

I’m using this dashboard - https://dune.com/bulik/evm-contract-insights.

Functions

We can see, that majority of invokes is served by two functions (out of 20 possible) with a relatively high number of unique callers compared to other less frequent functions.

Callers

The composition of callers (wallets acting directly with the contract) shows that usage of the contract is probably privileged as we see a relatively fairly distributed proportion of invokes across not so many callers (127 total). We can also see, that the mostly calling addresses are targeting at most 2 unique functions - probably the most called ones from the previous chart.

Digging into functions

To understand the purpose of individual functions (in case the contract is not verified) I recommend looking at several transactions invoking the selected function. Digging into transaction details for other sub-called contracts, transferred tokens, or other common actions may help to get the hunch of the function’s goal.

The advanced technique may include looking into a decompiled contract with a tool like https://library.dedaub.com/ or using an enhanced transaction analyzer like ethtx.info. You can check the flow of decompiled code, inspect traces and their ordering or look into contract memory changes to improve your guess.

Using the mentioned approaches I ended up with the following overview, where the functions are categorized by their purpose. I picked only the interesting functions, which have been used at least once.

Most used functions and their purpose
Most used functions and their purpose

Functions in the settings group are responsible for adding or removing values in/from slots. Slots are the storage of the contract, in this contract case, all the accessed slots are keeping a list of ETH addresses. As I found later each slot represents some kind of whitelist for certain actions.

Functions in the operations group are responsible for operations with funds but without ability to transfer/move them.

Functions in the funds group are responsible for managing the funds - call other whitelisted contracts (mostly LPs) with arbitrary action (mostly swap), perform swap on whitelisted UNI-V3 pool, transfer funds to whitelisted destination.

Slots

Looking into some frequently modified storage slots and their estimated meaning turns out that the contract tries to somehow limit the environment for the funds held by the contract. When a certain function operating with funds is called, the contract is always checking for permission to do so - who is the sender of action, which contract is being called or where are the funds going, etc.

  • Slot 1 - a whitelist of callable contracts checked in those functions:

    • call contract with given data,

    • swap via UNI-V3,

    • approve ERC-20.

  • Slot 2 - a whitelist of addresses allowed to call those functions:

    • wrap ETH,

    • unwrap ETH,

    • call contract with given data,

    • swap via UNI-V3.

  • Slot 3 - a whitelist of addresses allowed to call those functions:

    • transfer,

    • unwrap ETH, and send to the address.

  • Slot 4 - a whitelist of possible transfer destination addresses:

    • transfer,

    • unwrap ETH, and send to the address.

Updates of the slot values are controlled via an additional signature in the update transaction, only signatures from addresses in slot 5 are accepted.

Wintermute’s ignorance?

In blocks preceding the attack (ie. block at height 15572800), the compromised wallet could be found in the slot 2 whitelist. Does it mean, that Wintermute ignored the possible risk announced by 1inch on the 15. of September?

Let’s look into some interesting transactions handling slot modifications around the corresponding date, especially on the removing ones.

  • A, Slot 1, 2022-09-14, in this transaction many vanity addresses were attempted to be removed from slot 1, including the later compromised 0x0000000fe6a…, the catch is that none of the vanity addresses was in this slot previously. No change, only wasted gas.

  • B, Slot 1, 2022-09-15, regular transaction reducing the slot 1 list by 2 addresses.

  • C, Slot 1, 2022-09-15, again failed attempt, the addresses in the list were the same as in transaction E. Wrongly chosen slot to update, no change.

  • D, Slot 1, 2022-09-15, regular transaction reducing the slot 1 list by 3 addresses.

  • E, Slot 2, 2022-09-15, this is the only transaction before the exploit actually reduced the list of addresses in slot 2, but no vanity addresses in the list.

  • F, Slot 3, 2022-09-15, regular transaction reducing the slot 3 list by 1 address.

From the listed transaction and their results is obvious, that someone failed to choose correctly the slot he wanted to update. Is suspicious, that wasted transaction C was later run again within the correct slot (transaction E), but wasted transaction A wasn’t run again within the correct slot. Mistake or intent? Anyway, running this update within the correct slot would prevent the attack.

Activity of vanity addresses (from list in transaction A)
Activity of vanity addresses (from list in transaction A)

Attack approach

So about 5 days after publishing the Profanity vulnerability, many of the previously used vanity addresses, including the compromised one, are still sitting in the slot 2 whitelist. During this time the attacker probably already had revealed the private key and he was about to start acting on-chain.

He had to find an approach how to move funds from the managing contract address to some address he controls. Regular ERC-20 transfer is not an option because of whitelisted destination addresses and also due to the absence of compromised wallet in slot 3. Adding any item to any slot is not in his power, as the addresses permissioned for slot updates (listed in slot 5) are only non-vanity ones.

He needs to use some of the contracts listed in slot 1. Looking briefly into this list you can find a mix of unverified and verified contracts (ie. balancer pools for specific pairs, airswap contracts, 0x proxy, etc.). Most of the contracts are used to swap 2 tokens without the actual change of wealth/value owner, that is not what is the attacker looking for.

But in this list is also a 1inch router contract, which has a (magic) function unoswap. This function works as an interface for using a list of any UniSwap like pools. You specify your input token, a list of pools to perform chain swaps, and the amount of tokens you require to be returned at the end of the process. What if a hacker creates a new pool that simply won’t return any token to the sender? That’s exactly what he did. How did he figure it out? No idea, probably a combination of experience and advanced tooling.

After setting up the things, refilling the compromised wallet with some ETHs to pay fees, and creating the fake pool contract, the draining could start. In a total of 112 transactions, various tokens were “swapped” in this fake pool and from the pool then transferred into the attacker EOA wallet.

Other findings and following questions

Why the draining contract was refilled from CEX? During the draining, the contract was several times refilled from CEX, especially with stablecoins. I obviously don’t know the true reason, but my best guess is, that some robot monitored that contract is running out of funds, so he managed the withdrawal from CEX.

Any other usage of 1inch router contract?
The first time the 1inch router was called from the contract was during the exploit. Although added into slot 1 and approved for spending tokens was in May already.

Sum up

In this article, I tried to cover (with some assumptions) the logic and purpose of Wintermute’s on-chain contract. With advanced tooling, I revealed, that someone made mistake (hopefully) when he was maintaining the permissions inside the contract. I also shortly described the process of the attack and covered the possibilities of how the attacker could find out the importance of the compromised wallet.

Follow me on Twitter or reach me at Telegram.
Shout-out to Alte Mo for the cover photo.

Subscribe to Bulik
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from Bulik

Skeleton

Skeleton

Skeleton