<100 subscribers


A surprisingly simple lending pool allows anyone to deposit ETH, and withdraw it at any point in time. This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system. You must take all ETH from the lending pool.
mapping (address=>uint256) private balances
It is used for flash Loan Pool management and cannot be accessed from outside because it is a private access indicator.
function deposit() external payable
You can see that the value of the msg.value is stored in the state variable balances mapping as a key-value pair, where the key is the address of msg.sender. Anyone can call this function from the outside and send ether to it using the payable directive, which will add an entry to the balances mapping for the sender's address.
function withdraw() external
In the state variable balances mapping, the value is extracted based on the corresponding key for the msg.sender and assigned to the amountToWithdraw variable.
This logic configuration allows for the withdrawal of funds while managing the user's balance through the balances variable.
After temporarily allocating the value of amountToWithdraw to a local variable, the balances mapping value for the address is set to 0. You can see that the value is passed to msg.sender while executing the last line of payable(msg.sender).sendValue(amountToWithdraw).
In this part, we use the sendValue method to send ether.
Let's analyze it in depth. It is implemented as a method inside the openzeppelin/<->/Address.sol module. As stated in the development specification, the call command is used to transmit ether on the internal logic. In general, each of the methods transfer, send, and call is used to send ether.
The big differences are:
transfer : Consumes 2300 gas, and an error occurs in case of failure
send : consumes 2300 gas, returns true or false whether success or not
call : Consuming variable gas and receiving _to Ether. Use the form _to.call{value: msg.value, gas:}(””). Gas is unspecified due to reentrancy attack.
If the SLOAD operation consumes 5000 gas, transfer, send cannot be used, so the call command is used.
If the SLOAD operation consumes 5000 gas, transfer, send cannot be used, so the call command is used.

function flashLoan(uint256 amount) external
✅ After allocating the ether value of the current contract to the value of the amount variable received as a parameter and the local variable balanceBefore, we check whether the loan can be processed by comparing them.
Call the execute function defined in the IFlashLoanEtherReceiver(msg.sender).execute{value: amount}() Interface. In this case, the amount variable received as a parameter is used for the value property value. However, the execute function is not implemented internally, so it does not perform specific handling.
✅ At the end of the flash Loan logic, it is determined whether the loan has been properly collected.
It can be seen that the flash loan logic was not implemented properly. Because proper handling is not implemented, it does not work correctly, and if an attacker deploys a contract that arbitrarily implements the execute function, an attack outside of the intended logic is possible.
Next, the deposit/withdraw functions can control the value of the balances mapping in a way that anyone can use externally. At this time, by applying the payable(msg.sender).sendValue(...) pattern implemented in the withdraw function, the attacker can become a medium through which ether can be delivered to the implemented contract.
To exploit the target contract, we will build an exploit contract. Since the execute function is called by calling the flashLoan function in the attack target's logic, we will first call the flashLoan function.
sideEntranceLenderPool.flashLoan(address(sideEntranceLenderPool).balance); ← Because ether is actually stored as the target contract’s balance value, the value is passed, and when the execute function is executed, the value is converted to the target contract’s balance. Make settings.
When jumping to the execute function address, internally, by depositing the value before the exploit contract address, it is saved in the ‘balances’ mapping of the target contract. At this point, the address of the exploit contract distributed by the attacker is set as the key value, and the ether value of the SideEntranceLenderPool contract is set as the “value” value.
After returning the call, call sideEntranceLenderPool.withdraw(); to extract the ether value assigned to the balances mapping and send the ether to the actual exploit contract address. (target contract → exploit contract)
Since we moved the ether, we steal all the balance by sending it again to the attacker’s address.

Thank you for the @tinchoabbate that made a good wargame.
A surprisingly simple lending pool allows anyone to deposit ETH, and withdraw it at any point in time. This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system. You must take all ETH from the lending pool.
mapping (address=>uint256) private balances
It is used for flash Loan Pool management and cannot be accessed from outside because it is a private access indicator.
function deposit() external payable
You can see that the value of the msg.value is stored in the state variable balances mapping as a key-value pair, where the key is the address of msg.sender. Anyone can call this function from the outside and send ether to it using the payable directive, which will add an entry to the balances mapping for the sender's address.
function withdraw() external
In the state variable balances mapping, the value is extracted based on the corresponding key for the msg.sender and assigned to the amountToWithdraw variable.
This logic configuration allows for the withdrawal of funds while managing the user's balance through the balances variable.
After temporarily allocating the value of amountToWithdraw to a local variable, the balances mapping value for the address is set to 0. You can see that the value is passed to msg.sender while executing the last line of payable(msg.sender).sendValue(amountToWithdraw).
In this part, we use the sendValue method to send ether.
Let's analyze it in depth. It is implemented as a method inside the openzeppelin/<->/Address.sol module. As stated in the development specification, the call command is used to transmit ether on the internal logic. In general, each of the methods transfer, send, and call is used to send ether.
The big differences are:
transfer : Consumes 2300 gas, and an error occurs in case of failure
send : consumes 2300 gas, returns true or false whether success or not
call : Consuming variable gas and receiving _to Ether. Use the form _to.call{value: msg.value, gas:}(””). Gas is unspecified due to reentrancy attack.
If the SLOAD operation consumes 5000 gas, transfer, send cannot be used, so the call command is used.
If the SLOAD operation consumes 5000 gas, transfer, send cannot be used, so the call command is used.

function flashLoan(uint256 amount) external
✅ After allocating the ether value of the current contract to the value of the amount variable received as a parameter and the local variable balanceBefore, we check whether the loan can be processed by comparing them.
Call the execute function defined in the IFlashLoanEtherReceiver(msg.sender).execute{value: amount}() Interface. In this case, the amount variable received as a parameter is used for the value property value. However, the execute function is not implemented internally, so it does not perform specific handling.
✅ At the end of the flash Loan logic, it is determined whether the loan has been properly collected.
It can be seen that the flash loan logic was not implemented properly. Because proper handling is not implemented, it does not work correctly, and if an attacker deploys a contract that arbitrarily implements the execute function, an attack outside of the intended logic is possible.
Next, the deposit/withdraw functions can control the value of the balances mapping in a way that anyone can use externally. At this time, by applying the payable(msg.sender).sendValue(...) pattern implemented in the withdraw function, the attacker can become a medium through which ether can be delivered to the implemented contract.
To exploit the target contract, we will build an exploit contract. Since the execute function is called by calling the flashLoan function in the attack target's logic, we will first call the flashLoan function.
sideEntranceLenderPool.flashLoan(address(sideEntranceLenderPool).balance); ← Because ether is actually stored as the target contract’s balance value, the value is passed, and when the execute function is executed, the value is converted to the target contract’s balance. Make settings.
When jumping to the execute function address, internally, by depositing the value before the exploit contract address, it is saved in the ‘balances’ mapping of the target contract. At this point, the address of the exploit contract distributed by the attacker is set as the key value, and the ether value of the SideEntranceLenderPool contract is set as the “value” value.
After returning the call, call sideEntranceLenderPool.withdraw(); to extract the ether value assigned to the balances mapping and send the ether to the actual exploit contract address. (target contract → exploit contract)
Since we moved the ether, we steal all the balance by sending it again to the attacker’s address.

Thank you for the @tinchoabbate that made a good wargame.
Share Dialog
Share Dialog
No comments yet