Damn Vulnerable DeFi — Challenge #8 Walkthrough

Automated Market Makers (AMMs) like Uniswap provide many essential services to the DeFi ecosystem including on-chain price feeds. These price oracles are used by lending, derivatives, stable coins, and other applications. Unfortunately, it is possible to manipulate these price feeds which resulted in several multi-million dollar hacks.

In the next challenge, we will develop one such exploit against an overly trusting lending pool. Here is the description:

There's a huge lending pool borrowing Damn Valuable Tokens (DVTs), where you first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 10000 DVTs in liquidity.There's a DVT market opened in an Uniswap v1 exchange, currently with 10 ETH and 10 DVT in liquidity.Starting with 100 ETH and 100 DVTs in balance, you must steal as many tokens as possible from the lending pool. And at the end of the attack, your ETH balance shouldn't have decreased.

Let’s start with the puppet.challenge.js test file to observe how the challenge is set up:

In the snippet above, we initialize a new Uniswap factory and deploy DVL/ETH Liquidity Pool (LP). Next we deploy the target lending pool and pass it the address of the LP. Let’s take a look at the source for the lending pool to see how it uses the Uniswap pair:

The key element here is the computeOraclePrice() function which calculates the current exchange rate by calculating the ETH to DVL ratio in the pool.

The price calculation does not take into the account that it may be possible to purchase a significant portion of ETH in the pool to significantly drop the price of DVL available for lending. Let’s experiment with this scenario and see just how much we can borrow with the following exploit script which tracks attacker, Uniswap, and lending pool accounts as well as the oracle price:

Executing the above on the console, I’ve received some very curious results:

Notice that after we sell all of our DVL on Uniswap, the liquidity pool has 110 DVL tokens and about 0.911 ETH. The expected ETH to DVL ratio is supposed to be 0.00828706389326261706 which is great. However, lending pool’s price oracle function returns just 0! Let’s look more closely at the oracle function to see what could be causing this:

function computeOraclePrice() public view returns (uint256) {
return uniswapOracle.balance.div(token.balanceOf(uniswapOracle));
}

The thing about Solidity arithmetic operations is that they are integer based. So when we took the above ETH value and divided it by the much larger DVL the result was simply rounded to 0. What’s even more interesting is that the difference between ETH and DVL does not need to be that large. In fact even a single Wei drop in ETH relative to DVL will result in the price oracle rounding up the result of the division to 0. Let me demonstrate that in a quick experiment below:

Running the above exchange of just 2 Wei worth of DVL still makes the computeOraclePrice() return 0:

At this point we can craft the final attack script where we can “borrow” the entire pool for free! I will increase the amount of DVL we sell in order to cover gas fees for all of these transactions, a requirement in the challenge:

Running the above script we can successfully beat the challenge:

References

Blockchain Security, Malware Analysis, Incident Response, Pentesting, BlockThreat.net

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store