Skip to content

Commit

Permalink
Update ethernaut level
Browse files Browse the repository at this point in the history
  • Loading branch information
6boris committed Dec 27, 2024
1 parent fedfd44 commit 4e398a7
Show file tree
Hide file tree
Showing 21 changed files with 776 additions and 33 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ foundry/broadcast
.pnp.*
lcov.info
yarn.lock


build
.idea
6 changes: 1 addition & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
"modules": true
},
"npm.exclude": "/foundry/**/lib/**",
// "solidity.formatter": "prettier",
"solidity.formatter": "forge",
"solidity.packageDefaultDependenciesContractsDirectory": "contracts",
"solidity.packageDefaultDependenciesDirectory": "modules"
// "editor.defaultFormatter": "esbenp.prettier-vscode",
// "editor.formatOnSave": true
"editor.formatOnSave": true
}
19 changes: 19 additions & 0 deletions contracts/CTF/Ethernaut/30_HigherOrder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HigherOrder {
address public commander;

uint256 public treasury;

function registerTreasury(uint8 treasury_slot) public {
assembly {
sstore(treasury_slot, calldataload(4))
}
}

function claimLeadership() public {
if (treasury > 255) commander = msg.sender;
else revert("Only members of the Higher Order can become Commander");
}
}
72 changes: 72 additions & 0 deletions contracts/CTF/Ethernaut/31_Stake.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Level } from "./00_Ethernaut.sol";

contract Stake {
uint256 public totalStaked;
mapping(address => uint256) public UserStake;
mapping(address => bool) public Stakers;
address public WETH;

constructor(address _weth) payable {
totalStaked += msg.value;
WETH = _weth;
}

function StakeETH() public payable {
require(msg.value > 0.001 ether, "Don't be cheap");
totalStaked += msg.value;
UserStake[msg.sender] += msg.value;
Stakers[msg.sender] = true;
}

function StakeWETH(uint256 amount) public returns (bool) {
require(amount > 0.001 ether, "Don't be cheap");
(, bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender, address(this)));
require(bytesToUint(allowance) >= amount, "How am I moving the funds honey?");
totalStaked += amount;
UserStake[msg.sender] += amount;
(bool transfered,) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender, address(this), amount));
Stakers[msg.sender] = true;
return transfered;
}

function Unstake(uint256 amount) public returns (bool) {
require(UserStake[msg.sender] >= amount, "Don't be greedy");
UserStake[msg.sender] -= amount;
totalStaked -= amount;
(bool success,) = payable(msg.sender).call{ value: amount }("");
return success;
}

function bytesToUint(bytes memory data) internal pure returns (uint256) {
require(data.length >= 32, "Data length must be at least 32 bytes");
uint256 result;
assembly {
result := mload(add(data, 0x20))
}
return result;
}
}

contract StakeFactory is Level {
address _dweth = address(0);

function createInstance(address) public payable override returns (address) {
return address(new Stake(address(_dweth)));
}

function validateInstance(address payable _instance, address _player) public view override returns (bool) {
Stake instance = Stake(_instance);
return _instance.balance != 0 && instance.totalStaked() > _instance.balance && instance.UserStake(_player) == 0
&& instance.Stakers(_player);
}
}

contract StakeHack {
function attack(Stake _target) public payable {
_target.StakeETH{ value: msg.value }();
}
}
152 changes: 152 additions & 0 deletions contracts/CTF/Ethernaut/32_Impersonator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { Ownable } from "@openzeppelin/contracts-v4.7.3/access/Ownable.sol";
import { Level } from "./00_Ethernaut.sol";

// SlockDotIt ECLocker factory
contract Impersonator is Ownable {
uint256 public lockCounter;
ECLocker[] public lockers;

event NewLock(address indexed lockAddress, uint256 lockId, uint256 timestamp, bytes signature);

constructor(uint256 _lockCounter) {
lockCounter = _lockCounter;
}

function deployNewLock(bytes memory signature) public onlyOwner {
// Deploy a new lock
ECLocker newLock = new ECLocker(++lockCounter, signature);
lockers.push(newLock);
emit NewLock(address(newLock), lockCounter, block.timestamp, signature);
}
}

contract ECLocker {
uint256 public immutable lockId;
bytes32 public immutable msgHash;
address public controller;
mapping(bytes32 => bool) public usedSignatures;

event LockInitializated(address indexed initialController, uint256 timestamp);
event Open(address indexed opener, uint256 timestamp);
event ControllerChanged(address indexed newController, uint256 timestamp);

error InvalidController();
error SignatureAlreadyUsed();

/// @notice Initializes the contract the lock
/// @param _lockId uinique lock id set by SlockDotIt's factory
/// @param _signature the signature of the initial controller
constructor(uint256 _lockId, bytes memory _signature) {
// Set lockId
lockId = _lockId;

// Compute msgHash
bytes32 _msgHash;
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 28 bytes
mstore(0x1C, _lockId) // 32 bytes
_msgHash := keccak256(0x00, 0x3c) //28 + 32 = 60 bytes
}
msgHash = _msgHash;

// Recover the initial controller from the signature
address initialController = address(1);
assembly {
let ptr := mload(0x40)
mstore(ptr, _msgHash) // 32 bytes
mstore(add(ptr, 32), mload(add(_signature, 0x60))) // 32 byte v
mstore(add(ptr, 64), mload(add(_signature, 0x20))) // 32 bytes r
mstore(add(ptr, 96), mload(add(_signature, 0x40))) // 32 bytes s
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
initialController, // Address of `ecrecover`.
ptr, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
if iszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
initialController := mload(0x00)
mstore(0x40, add(ptr, 128))
}

// Invalidate signature
usedSignatures[keccak256(_signature)] = true;

// Set the controller
controller = initialController;

// emit LockInitializated
emit LockInitializated(initialController, block.timestamp);
}

/// @notice Opens the lock
/// @dev Emits Open event
/// @param v the recovery id
/// @param r the r value of the signature
/// @param s the s value of the signature
function open(uint8 v, bytes32 r, bytes32 s) external {
address add = _isValidSignature(v, r, s);
emit Open(add, block.timestamp);
}

/// @notice Changes the controller of the lock
/// @dev Updates the controller storage variable
/// @dev Emits ControllerChanged event
/// @param v the recovery id
/// @param r the r value of the signature
/// @param s the s value of the signature
/// @param newController the new controller address
function changeController(uint8 v, bytes32 r, bytes32 s, address newController) external {
_isValidSignature(v, r, s);
controller = newController;
emit ControllerChanged(newController, block.timestamp);
}

function _isValidSignature(uint8 v, bytes32 r, bytes32 s) internal returns (address) {
address _address = ecrecover(msgHash, v, r, s);
require(_address == controller, InvalidController());

bytes32 signatureHash = keccak256(abi.encode([uint256(r), uint256(s), uint256(v)]));
require(!usedSignatures[signatureHash], SignatureAlreadyUsed());

usedSignatures[signatureHash] = true;

return _address;
}
}

contract ImpersonatorFactory is Level {
function createInstance(address _player) public payable override returns (address) {
_player;
Impersonator impersonator = new Impersonator(1336);
bytes memory signature = abi.encode(
[
uint256(
11_397_568_185_806_560_130_291_530_949_248_708_355_673_262_872_727_946_990_834_312_389_557_386_886_033
),
uint256(
54_405_834_204_020_870_944_342_294_544_757_609_285_398_723_182_661_749_830_189_277_079_337_680_158_706
),
uint256(27)
]
);
impersonator.deployNewLock(signature);
return address(impersonator);
}

function validateInstance(address payable _instance, address _player) public view override returns (bool) {
_player;
Impersonator instance = Impersonator(_instance);
ECLocker locker = instance.lockers(0);
return locker.controller() == address(0);
}
}
73 changes: 73 additions & 0 deletions contracts/CTF/Ethernaut/33_MagicAnimalCarousel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { Level } from "./00_Ethernaut.sol";

contract MagicAnimalCarousel {
uint16 public constant MAX_CAPACITY = type(uint16).max;
uint256 constant ANIMAL_MASK = uint256(type(uint80).max) << 160 + 16;
uint256 constant NEXT_ID_MASK = uint256(type(uint16).max) << 160;
uint256 constant OWNER_MASK = uint256(type(uint160).max);

uint256 public currentCrateId;
mapping(uint256 crateId => uint256 animalInside) public carousel;

error AnimalNameTooLong();

constructor() {
carousel[0] ^= 1 << 160;
}

function setAnimalAndSpin(string calldata animal) external {
uint256 encodedAnimal = encodeAnimalName(animal) >> 16;
uint256 nextCrateId = (carousel[currentCrateId] & NEXT_ID_MASK) >> 160;

require(encodedAnimal <= uint256(type(uint80).max), AnimalNameTooLong());
carousel[nextCrateId] = (carousel[nextCrateId] & ~NEXT_ID_MASK) ^ (encodedAnimal << 160 + 16)
| ((nextCrateId + 1) % MAX_CAPACITY) << 160 | uint160(msg.sender);

currentCrateId = nextCrateId;
}

function changeAnimal(string calldata animal, uint256 crateId) external {
address owner = address(uint160(carousel[crateId] & OWNER_MASK));
if (owner != address(0)) {
require(msg.sender == owner);
}
uint256 encodedAnimal = encodeAnimalName(animal);
if (encodedAnimal != 0) {
// Replace animal
carousel[crateId] = (encodedAnimal << 160) | (carousel[crateId] & NEXT_ID_MASK) | uint160(msg.sender);
} else {
// If no animal specified keep same animal but clear owner slot
carousel[crateId] = (carousel[crateId] & (ANIMAL_MASK | NEXT_ID_MASK));
}
}

function encodeAnimalName(string calldata animalName) public pure returns (uint256) {
require(bytes(animalName).length <= 12, AnimalNameTooLong());
return uint256(bytes32(abi.encodePacked(animalName)) >> 160);
}
}

contract MagicAnimalCarouselFactory is Level {
function createInstance(address _player) public payable override returns (address) {
_player;
MagicAnimalCarousel magicAnimalCarousel = new MagicAnimalCarousel();
return address(magicAnimalCarousel);
}

function validateInstance(address payable _instance, address _player) public override returns (bool) {
_player;
MagicAnimalCarousel instance = MagicAnimalCarousel(_instance);
// Store a goat in the box
string memory goat = "Goat";
instance.setAnimalAndSpin(goat);

// Goat should be mutated
uint256 currentCrateId = instance.currentCrateId();
uint256 animalInBox = instance.carousel(currentCrateId) >> 176;
uint256 goatEnc = uint256(bytes32(abi.encodePacked(goat))) >> 176;
return animalInBox != goatEnc;
}
}
Empty file added contracts/Tool/.gitkeep
Empty file.
35 changes: 35 additions & 0 deletions contracts/Tool/Batch/BatchTransfer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract BatchTransfer {
function BatchTransferERC20(
ERC20 token,
address[] calldata toAddressList,
uint256[] calldata toAmountList
)
external
{
require(toAddressList.length == toAmountList.length, "Params not match");
for (uint256 idx = 0; idx < toAddressList.length; idx++) {
require(
token.transferFrom(msg.sender, toAddressList[idx], toAmountList[idx]), "Transfer ERC20 Token Failed"
);
}
}

function BatchTransferERC721(
ERC721 token,
address[] calldata toAddressList,
uint256[] calldata toTokenIDList
)
external
{
require(toAddressList.length == toTokenIDList.length, "Params not match");
for (uint256 idx = 0; idx < toAddressList.length; idx++) {
token.transferFrom(msg.sender, toAddressList[idx], toTokenIDList[idx]);
}
}
}
Loading

0 comments on commit 4e398a7

Please sign in to comment.