
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
<100 subscribers
<100 subscribers
This is my solution to the Alien Codex ethernaut challenge. During experimentation, I found that the EVM prevents exploits like this from happening in current versions. The contract for the problem is as follows
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import '../helpers/Ownable-05.sol';
contract AlienCodex is Ownable {
bool public contact;
bytes32[] public codex;
modifier contacted() {
assert(contact);
_;
}
function make_contact() public {
contact = true;
}
function record(bytes32 _content) contacted public {
codex.push(_content);
}
function retract() contacted public {
codex.length--;
}
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}
The objective is to take ownership of the contract. As we can see, the contract inherits from Ownable. Ownable has one item in storage which is the address of the deployer of the contract. Thus, the _owner variable will be in the first storage slot. Since an address is only 20 bytes, the first storage slot gets packed in with the bool , contact that is defined after it. Sure enough, checking the storage for the first slot, we get
await web3.eth.getStorageAt(contract.address, 0)
"0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272"
Here, we can see that the address that deployed the contract is da5b3fb76c78b6edee6be8f11a1c31ecfb02b272 and the boolean contact is set to true which is the first 1. Note that when tightly packing, the EVM stores values from left to right.
The contract can record new values into the codex by calling the record function. This simply pushes a new value into the array. To remove values from the array, this contract does something interesting with retract. Instead of trying to remove the actual value, it just reduces the length of the array. This makes it so that the ABI will throw an error when trying to access values beyond the length. This is all well and good, but there is no check for underflow here which is a potential source for an exploit.
The final function in the contract is revise. This simply allows the caller to set any value in the codex array.
The key breakthrough to tackling this problem is to understand that storage slots wrap around - similar to unsigned integers. This means that after slot 2^256 - 1, the next slot is slot 0. This means that if we can set a high enough value in the array, the array can be used to effectively write to any slot in the contract.
The first step to take here is to “open” up the array to the entire storage space of the contract. By forcing an underflow by calling retract when there are no values, the length of the array gets set to 0xfff…fff. The address of the 0 slot can then be found by simply getting the difference between this and the array slot location and adding 1. Setting this new index with revise solves the challenge
This is my solution to the Alien Codex ethernaut challenge. During experimentation, I found that the EVM prevents exploits like this from happening in current versions. The contract for the problem is as follows
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import '../helpers/Ownable-05.sol';
contract AlienCodex is Ownable {
bool public contact;
bytes32[] public codex;
modifier contacted() {
assert(contact);
_;
}
function make_contact() public {
contact = true;
}
function record(bytes32 _content) contacted public {
codex.push(_content);
}
function retract() contacted public {
codex.length--;
}
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}
The objective is to take ownership of the contract. As we can see, the contract inherits from Ownable. Ownable has one item in storage which is the address of the deployer of the contract. Thus, the _owner variable will be in the first storage slot. Since an address is only 20 bytes, the first storage slot gets packed in with the bool , contact that is defined after it. Sure enough, checking the storage for the first slot, we get
await web3.eth.getStorageAt(contract.address, 0)
"0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272"
Here, we can see that the address that deployed the contract is da5b3fb76c78b6edee6be8f11a1c31ecfb02b272 and the boolean contact is set to true which is the first 1. Note that when tightly packing, the EVM stores values from left to right.
The contract can record new values into the codex by calling the record function. This simply pushes a new value into the array. To remove values from the array, this contract does something interesting with retract. Instead of trying to remove the actual value, it just reduces the length of the array. This makes it so that the ABI will throw an error when trying to access values beyond the length. This is all well and good, but there is no check for underflow here which is a potential source for an exploit.
The final function in the contract is revise. This simply allows the caller to set any value in the codex array.
The key breakthrough to tackling this problem is to understand that storage slots wrap around - similar to unsigned integers. This means that after slot 2^256 - 1, the next slot is slot 0. This means that if we can set a high enough value in the array, the array can be used to effectively write to any slot in the contract.
The first step to take here is to “open” up the array to the entire storage space of the contract. By forcing an underflow by calling retract when there are no values, the length of the array gets set to 0xfff…fff. The address of the 0 slot can then be found by simply getting the difference between this and the array slot location and adding 1. Setting this new index with revise solves the challenge
No activity yet