https://github.com/kyrers
Capture The Ether Miscellaneous Solutions
In this post, I will share my explanations for the Miscellaneous section of the Capture The Ether challenges. There are plenty of solutions around the web - my goal was to solve the challenges locally, avoiding Etherscan when possible, and writing code locally. You can find the code here. Let’s begin.Assume OwnershipThis challenge might be the easiest. Solidity now allows you to use the constructor keyword so constructors stand out. However, the challenge contract doesn't use that keywor...
Damn Vulnerable DeFi 6 - 10 Solutions
In this post, I will share my explanations for DamnVulnerableDeFI challenges 6 through 10. You can find the code here. Let’s begin.SelfieAs per usual, this challenge involves a pool providing flash loans of 1.5 million DVT tokens. Our goal is to take them all. Fortunately, we can immediately see that the pool has a drainAllFunds(...) function, which sends all pool DVT tokens to the address passed as a parameter. Unfortunately for us, it can only be called by governance. SelfiePool is the firs...
Capture The Ether Miscellaneous Solutions
In this post, I will share my explanations for the Miscellaneous section of the Capture The Ether challenges. There are plenty of solutions around the web - my goal was to solve the challenges locally, avoiding Etherscan when possible, and writing code locally. You can find the code here. Let’s begin.Assume OwnershipThis challenge might be the easiest. Solidity now allows you to use the constructor keyword so constructors stand out. However, the challenge contract doesn't use that keywor...
Damn Vulnerable DeFi 6 - 10 Solutions
In this post, I will share my explanations for DamnVulnerableDeFI challenges 6 through 10. You can find the code here. Let’s begin.SelfieAs per usual, this challenge involves a pool providing flash loans of 1.5 million DVT tokens. Our goal is to take them all. Fortunately, we can immediately see that the pool has a drainAllFunds(...) function, which sends all pool DVT tokens to the address passed as a parameter. Unfortunately for us, it can only be called by governance. SelfiePool is the firs...
https://github.com/kyrers

Subscribe to kyrers

Subscribe to kyrers
<100 subscribers
<100 subscribers
Share Dialog
Share Dialog
In this post, I will share my explanations for DamnVulnerableDeFI challenges 1 through 5.
You can find the code here.
Let’s begin.
If you're like me, your first instinct might be to drain the pool. However, as you read through the contracts, you'll hopefully realize that there's a much easier way to stop the pool from giving out flashloans.
Let's start with the ReceiverUnstoppable contract. It has two functions receiveTokens(...) which, as the comment says, will be called during the execution of the executeFlashLoan(...) function. Looking at the executeFlashLoan(...) function, we can see that it must be called by the owner and that it will in turn call the flashLoan(...) function of the UnstoppableLender contract, so let's go to that function. The flashLoan(...) function first checks that the loan amount is higher than 0 but not greater than the pool token balance. It then checks that the poolBalance value corresponds to the actual amount of tokens the pool has. Only after does it execute the flashloan and check that it has been repaid.
So, the flashLoan(...) function will revert in case the loan amount is invalid or the loan isn't repaid. These make sense. However, it will fail in one extra scenario - if the actual amount of tokens in the pool differs from the stateful poolBalance value, which raises the question: where is the poolBalance value updated? It's in the depositTokens(...) function of the pool, which was intended as the only way users would deposit tokens in the pool. But, considering our balance of 100 DVT tokens, what's stopping us from using transfer to deposit 1 DVT token in the pool?
Ok, so we're told exactly what to do: Drain FlashLoanReceiver funds. If you look at the contract it has three functions, but only receiveEther(...) actually does something - it receives the flashloan and pays it back. As the comment says, this function is called while the NaiveReceiverLenderPool contract flashLoan(...) function is executed. Let's take a look.
We can see that the flashLoan(...) function checks that there's enough ETH in the pool for the requested flashloan amount. Then, it checks that the borrower is a contract. Finally, it verifies if the flashloan plus the fixed fee of 1 ether has been paid back.
Do you see the issue? The borrower is passed as a parameter! This means that we can say that the borrower is the FlashLoanReceiver contract and request a flashloan of 0 ether, which will cost the FlashLoanReceiver 1 ether in fees. Do this enough times and the receiver is drained!
To do this in one transaction, we just need to deploy a contract that does the attack for us.
So far, this is the most to-the-point challenge. We have to drain 1000000 DVT from the TrusterLenderPool.
Looking at the flashloan(...) function we immediately see that it has two strange parameters: address target and bytes calldata data. Examining further we find that this is a classic flash loan function except that before checking that the loan is repaid, it unexpectedly calls the target address using the data parameters. Well, this is obviously our way in.
Since no checks are performed, we can simply say that we want to borrow 0 DVT tokens, the target is the DVT token contract and the data is a call to the approve(...) function, which allows us to approve a transfer from the pool to ourselves of any amount of the pool DVT tokens. So two transactions are needed: one to call flashloan(...) in order to sneak in that approve(...) transaction, and another to transferFrom() to send the DVT tokens to ourselves.
To do this in one transaction, we just need to deploy a contract that does the attack for us.
Once again, we have a 1000 ether pool that provides free flashloans. Our job is to drain the pool.
Looking at the SideEntranceLenderPool contract we can see that it allows users to deposit and withdraw funds from the pool. Of course, it also allows us to use the flashloan(...) function to request a flash loan.
Aside from repaying the flash loan, this flashloan(...) function also requires that the borrower implement the IFlashLoanEtherReceiver interface, which has the function execute() responsible for handling the flash loan funds.
So one thing is for sure, we need to implement an attacker contract that asks for the flash loan and receives it in the execute() function. But what do we do with it? Remember the deposit(...) and withdraw(...) functions? We just deposit the funds back in the pool, which will make the flash loan successful and set the funds as the property of our attacker contract, which in turn means we can withdraw those funds from the pool. All that's left to do is send the withdrawn funds from our contract to our wallet.
The first challenge where things get more complex. However, there are several tips in the challenge description that will provide us with a path to find the exploit.
First, we find out that there's a pool offering rewards in 5-day increments to users that deposit DVT tokens. We also discover that we have no DVT tokens, but that there's a pool offering free flash loans.
Let's look at the FlashLoanPool flashloan(...) function. It requires that the borrower is a deployed contract with the receiveFlashLoan(uint256) function to handle the flash loan. We now have a way of getting the DVT tokens, but how do we use them in regard to TheRewarderPool?
Looking at the pool the first thing we notice is that it uses three tokens: LiquidityToken, AccountingToken and RewardToken. Again, from the challenge description, it becomes obvious that we need to get some RewardToken tokens.
It seems that the only way to mint RewardToken is inside the distributeRewards(...) function. To execute that code, the function checks that rewards > 0 && !_hasRetrievedReward(msg.sender). Let's find a way to pass this verification.
hasRetrievedReward(msg.sender) will return false if we have never received a reward, which is true in our case - easy. rewards will be greater than 0 if we have a positive balance of AccountingToken tokens considering the last snapshot taken. Ok, so how do we get a positive balance of AccountingToken and our snapshot taken?
Snapshots are taken earlier in the distributeRewards(...) function execution via a call to _recordSnapshot(). However, to execute this call we need to pass the verification isNewRewardsRound() which checks that at least 5 days have passed since the last snapshot. This means that we need to wait 5 days before calling distributeRewards(...).
That was easy, but we still need to figure out how to get a positive balance of AccountingToken. Looking at the pool we see that AccountingToken are minted in the deposit(...) function, in proportion to the deposited LiquidityToken which is the DVT token. We also see that AccountingToken tokens are burned in the withdraw(...) function which, in addition to sending us our RewardToken, sends the DVT tokens back to us - allowing us to pay the flash loan.
We now have a clear picture of how to get RewardToken, and since we already know how to get our hands on free DVT tokens, we have all the information we need.
Let's recap what we know:
We know we have to get some amount of RewardToken to pass the challenge;
The only place where RewardToken is minted is in the TheRewarderPool distributeRewards(...) function;
To be able to reach that place of the code, the function needs that the caller:
Have a positive balance of AccountingToken since the last snapshot;
Have never received a reward;
To clear the verifications in the previous step, we need to:
Wait 5 days;
Deposit DVT tokens in the pool;
We have no DVT tokens, but we know how to get them for free provided we pay them back - which we can since we can call the pool withdraw(...) function which will give us our DVT tokens back after we have successfully gotten our RewardToken rewards;
All that's left is implementing a RewardAttacker contract that does all the above for us.
In this post, I will share my explanations for DamnVulnerableDeFI challenges 1 through 5.
You can find the code here.
Let’s begin.
If you're like me, your first instinct might be to drain the pool. However, as you read through the contracts, you'll hopefully realize that there's a much easier way to stop the pool from giving out flashloans.
Let's start with the ReceiverUnstoppable contract. It has two functions receiveTokens(...) which, as the comment says, will be called during the execution of the executeFlashLoan(...) function. Looking at the executeFlashLoan(...) function, we can see that it must be called by the owner and that it will in turn call the flashLoan(...) function of the UnstoppableLender contract, so let's go to that function. The flashLoan(...) function first checks that the loan amount is higher than 0 but not greater than the pool token balance. It then checks that the poolBalance value corresponds to the actual amount of tokens the pool has. Only after does it execute the flashloan and check that it has been repaid.
So, the flashLoan(...) function will revert in case the loan amount is invalid or the loan isn't repaid. These make sense. However, it will fail in one extra scenario - if the actual amount of tokens in the pool differs from the stateful poolBalance value, which raises the question: where is the poolBalance value updated? It's in the depositTokens(...) function of the pool, which was intended as the only way users would deposit tokens in the pool. But, considering our balance of 100 DVT tokens, what's stopping us from using transfer to deposit 1 DVT token in the pool?
Ok, so we're told exactly what to do: Drain FlashLoanReceiver funds. If you look at the contract it has three functions, but only receiveEther(...) actually does something - it receives the flashloan and pays it back. As the comment says, this function is called while the NaiveReceiverLenderPool contract flashLoan(...) function is executed. Let's take a look.
We can see that the flashLoan(...) function checks that there's enough ETH in the pool for the requested flashloan amount. Then, it checks that the borrower is a contract. Finally, it verifies if the flashloan plus the fixed fee of 1 ether has been paid back.
Do you see the issue? The borrower is passed as a parameter! This means that we can say that the borrower is the FlashLoanReceiver contract and request a flashloan of 0 ether, which will cost the FlashLoanReceiver 1 ether in fees. Do this enough times and the receiver is drained!
To do this in one transaction, we just need to deploy a contract that does the attack for us.
So far, this is the most to-the-point challenge. We have to drain 1000000 DVT from the TrusterLenderPool.
Looking at the flashloan(...) function we immediately see that it has two strange parameters: address target and bytes calldata data. Examining further we find that this is a classic flash loan function except that before checking that the loan is repaid, it unexpectedly calls the target address using the data parameters. Well, this is obviously our way in.
Since no checks are performed, we can simply say that we want to borrow 0 DVT tokens, the target is the DVT token contract and the data is a call to the approve(...) function, which allows us to approve a transfer from the pool to ourselves of any amount of the pool DVT tokens. So two transactions are needed: one to call flashloan(...) in order to sneak in that approve(...) transaction, and another to transferFrom() to send the DVT tokens to ourselves.
To do this in one transaction, we just need to deploy a contract that does the attack for us.
Once again, we have a 1000 ether pool that provides free flashloans. Our job is to drain the pool.
Looking at the SideEntranceLenderPool contract we can see that it allows users to deposit and withdraw funds from the pool. Of course, it also allows us to use the flashloan(...) function to request a flash loan.
Aside from repaying the flash loan, this flashloan(...) function also requires that the borrower implement the IFlashLoanEtherReceiver interface, which has the function execute() responsible for handling the flash loan funds.
So one thing is for sure, we need to implement an attacker contract that asks for the flash loan and receives it in the execute() function. But what do we do with it? Remember the deposit(...) and withdraw(...) functions? We just deposit the funds back in the pool, which will make the flash loan successful and set the funds as the property of our attacker contract, which in turn means we can withdraw those funds from the pool. All that's left to do is send the withdrawn funds from our contract to our wallet.
The first challenge where things get more complex. However, there are several tips in the challenge description that will provide us with a path to find the exploit.
First, we find out that there's a pool offering rewards in 5-day increments to users that deposit DVT tokens. We also discover that we have no DVT tokens, but that there's a pool offering free flash loans.
Let's look at the FlashLoanPool flashloan(...) function. It requires that the borrower is a deployed contract with the receiveFlashLoan(uint256) function to handle the flash loan. We now have a way of getting the DVT tokens, but how do we use them in regard to TheRewarderPool?
Looking at the pool the first thing we notice is that it uses three tokens: LiquidityToken, AccountingToken and RewardToken. Again, from the challenge description, it becomes obvious that we need to get some RewardToken tokens.
It seems that the only way to mint RewardToken is inside the distributeRewards(...) function. To execute that code, the function checks that rewards > 0 && !_hasRetrievedReward(msg.sender). Let's find a way to pass this verification.
hasRetrievedReward(msg.sender) will return false if we have never received a reward, which is true in our case - easy. rewards will be greater than 0 if we have a positive balance of AccountingToken tokens considering the last snapshot taken. Ok, so how do we get a positive balance of AccountingToken and our snapshot taken?
Snapshots are taken earlier in the distributeRewards(...) function execution via a call to _recordSnapshot(). However, to execute this call we need to pass the verification isNewRewardsRound() which checks that at least 5 days have passed since the last snapshot. This means that we need to wait 5 days before calling distributeRewards(...).
That was easy, but we still need to figure out how to get a positive balance of AccountingToken. Looking at the pool we see that AccountingToken are minted in the deposit(...) function, in proportion to the deposited LiquidityToken which is the DVT token. We also see that AccountingToken tokens are burned in the withdraw(...) function which, in addition to sending us our RewardToken, sends the DVT tokens back to us - allowing us to pay the flash loan.
We now have a clear picture of how to get RewardToken, and since we already know how to get our hands on free DVT tokens, we have all the information we need.
Let's recap what we know:
We know we have to get some amount of RewardToken to pass the challenge;
The only place where RewardToken is minted is in the TheRewarderPool distributeRewards(...) function;
To be able to reach that place of the code, the function needs that the caller:
Have a positive balance of AccountingToken since the last snapshot;
Have never received a reward;
To clear the verifications in the previous step, we need to:
Wait 5 days;
Deposit DVT tokens in the pool;
We have no DVT tokens, but we know how to get them for free provided we pay them back - which we can since we can call the pool withdraw(...) function which will give us our DVT tokens back after we have successfully gotten our RewardToken rewards;
All that's left is implementing a RewardAttacker contract that does all the above for us.
No activity yet