Fallback
- Description: The
Fallback
contract can be reclaimed ownership by anyone with fallback methods.
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
@> owner = msg.sender;
}
Fallout
- Description: Incorrect in the old constructor name brings on reclaiming ownership.
@> function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
- Proof of Code: As the
Fallout
contract is lower than0.8.0, i can not implement test in there. Just call
Fallout::Fal1out()` function to reclaim the ownership contract.
Coin Flip
-
Description:
CoinFlip
contract use the random side of coin byblock.number
. -
Recommended: Using VRF instead.
Telephone
- Description:
tx.origin
andmsg.sender
should be different if we use the other contract to callTelephone
contract.
Token
-
Description: Underflow / Overflow in version under
0.8.0
. -
Recommended: Using
^0.8.0
version orSafe Math
library.
Delegation
- Description: Careful of using delegate call.
Force
- Description:
Force
contract does not have any fallback or receive methods to receive ether. We can force this contract to receive ether by using other contract to destruct itself:
Vault
-
Description: This
Vault::password
is private so we can not read it. However, everything in blockchain is public, we will get it from storage ofVault
contract. -
Proof of Code: There are 2 variable with
bool
andbytes32
type storing at slot 0 and slot 1 in contract's memory becausebool
type is 32 bytes in slot 0 andbytes32
is in slot 1 (each slot has 32 bytes itself).
bool public locked;
bytes32 private password;
King
- Description:
King
contract is competitive contract with theking
will be the last sending the biggest ether or be sent byowner
. When other would be theking
, they will send ether to this contract larger or equal the lastprize
andKing
contract will send back lastking
their ether. What happen if lastking
is a contract without fallback or receive methods? The lastking
will be the king forever.
Re-entrancy
- Description: This
Reentrance
contract allows us to re-enter thewithdraw
function through other contract has receive or fallback methods containingReentrance::withdraw
call because theReentrance::donate
will send ether to the callee. The reason is that theReentrance
contract change the user's balance state after the invoker finished.
Elevator
- Description: Attack the
Elevator
contract by implementing the other contract with customisLastFloor
function.
Privacy
- Description:
Gatekeeper One
- Description:
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
The way to break gateOne
modifier is that using the other contract to call the function in GatekeeperOne
modifier gateTwo() {
require(gasleft() % 8191 == 0);
_;
}
We will use call
methods to call function with gas
property. The value of gas should be the number of dividable 8191, then i will use loop for applying gas value.
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
_;
}
uint64 k = uint64(_gateKey);
=> uint32(k) == uint16(k)
=> uint32(k) != uint64(_gateKey)
=> uint32(k) == uint16(uint160(tx.origin))
// uint32(k) == uint16(uint160(tx.origin))
// uint32(k) == uint16(k)
=> uint16(k) = uint16(uint160(tx.origin))
=> k = uint160(tx.origin)
// uint32(k) != uint64(_gateKey)
=> uint64 k64 = uint64(1 << 63) + uint64(k16)
Gatekeeper Two
- Description:
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
The way to break gateOne
modifier is that using the other contract to call the function in GatekeeperOne
modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0);
_;
}
This code is implied that the contract sender does not have any code. Therefore, we will use only constructor
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
Naught Coin
- Description: This token implement
ERC20
standard ofOpenzeppelin
and override thetransfer
function to apply the duration of time. However, they do not override thetransferFrom
function with the same functionality withtransfer
.
Preservation
- Description:
Preservation
contract use thedelegatecall
to call the libraries to set itsstoredTime
. To use thedelegatecall
the storage of callee must be match with the storage of caller. The position ofstoredTime
inLibraryContract
isslot0
but inPreservation
isslot4
, thenstoredTime
inLibraryContract
matched withtimeZone1Library
inPreservation
contract Preservation {
// public library contracts
@> address public timeZone1Library;
address public timeZone2Library;
address public owner;
@> uint256 storedTime;
.
.
.
}
contract LibraryContract {
// stores a timestamp
@> uint256 storedTime;
...
}
Recovery
-
Description:
Recovery
contract is a factory contract to produceSimpleToken
with corresponding inputs. We cannot know the newSimpleToken
contract address when finished because of not code factory verification but we can compute the created contract address based onsender
andnonce
(which is the number of address created`). -
Refer: How is the address of an Ethereum contract computed ?
Magic Number
- Description:
Alien Codex
- Description: The entire storage area is
2^256
and the array will expand to entire storage by the arithmetic underflow of array length. Usingretract
to expand the array to occupy entire storage and change the value ofslot0
byrevise
function to modify theowner
value stored inslot0
.
contract AlienCodex is Ownable {
bool public contact;
@> bytes32[] public codex;
...
function retract() public contacted {
codex.length--;
}
function revise(uint256 i, bytes32 _content) public contacted {
codex[i] = _content;
}
}
Denial
- Description: In the
withdraw
function, thepartner
andowner
will be transfer ether from contract. However,partner
is withdrawal withcall
methods andowner
is used bytransfer
. We can prevent the anyone calling thewithdraw
function by using all the gas limit in withdrawal transaction. To do this, we implement thepartner
is the receivable contract havingreceive
fallback consume all gas limits.
Shop
- Description: The
Shop
contract is dependent on theprice
function ofmsg.sender
. So we just make other contract havingprice
function return the value based onShop::isSold
variable
DEX
- Description: The
Dex
contract hasswap
function with the price calculated byamountOfSwap
andbalance
of token1 of contract andbalance
of token2 of contract. The ratio is 1:1. However, there is rounding issue in division, the ratio after swapping is not 1:1 as initially
function swap(address from, address to, uint256 amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapPrice(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
function getSwapPrice(address from, address to, uint256 amount) public view returns (uint256) {
return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
}
DexTwo
- Description: The
swap
function does not have any check thefrom
andto
token address, the malicious user can pass their virus token address tofrom
andto
address to exploit the contract.
function swap(address from, address to, uint256 amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
DoubleEntryPoint
- Description:
- The malicious user can drain all the
underlying
token (DoubleEntryPoint) stuck inCryptoVault
by callingCryptoVault::sweepToken
function with an argument asLegacyToken
token. The reason is thatLegacyToken
contract has a customizable weirdtransfer
function which will call theDoubleEntryPoint::transfer
ifLegacyToken::delegate
is set. - To protect this vulnerability, we recommend the
DetectionBot
contract which will detect whenDoubleEntryPoint::delegateTransfer
is called. - The
DetectionBot
contract will compare theorigSender
withCryptoVault
contract address. If equation, will callForta::raiseAlert
.
- The malicious user can drain all the
Motorbike
- Description:
- The
Motorbike
contract is proxy contract to delegatedelegatecall
toEngine
contract as an implementation contract. Usingselfdestruct
to break theEngine
contract, we have to takeover theEngine
contract to be able to callupgradeToAndCall
function. We will deploy aHack
contract to be an new implementation contract with ahack
function containingselfdestruct
. To takeover theEngine
contract, we have to callinitialize
function to setupgrader
to our wallet and callupgradeToAndCall
with arguments asHack
contract address and signature ofhack
function.
- The
Puzzle Wallet
- Description: The proxy and implementation contract does not match with storage slot each other. To become
PuzzleProxy::admin
, we will do step-by-step:- Become
owner
:owner
andpendingAdmin
variable is stored in same slot 0. So we can setpendingAdmin
to be able to changeowner
- Call
addToWhitelist
function to becomewhitelisted
- Drain all contract's balance, we can drain all the balance because
multicall
function accept re-entry it to calldeposit
2 times with only0.001 ether
. - Call
setMaxBalance
function withmsg.sender
as an argument casted touint256
.
- Become
Good Samaritan
-
Description: The
GoodSamaritan::requestDonation
function will be call by anyone if they need some tokens. However, this function is usingtry catch
to check the succeed ofWallet::donate10
function invoke and if this function is failed and the error return is equalabi.encodeWithSignature("NotEnoughBalance()")
the wallet will send all tokens to caller. We should userevert NotEnoughBalance()
in thenotify
function of ourHack
contract. -
POC:
contract GoodSamaritan {
...
function requestDonation() external returns (bool enoughBalance) {
// donate 10 coins to requester
try wallet.donate10(msg.sender) {
return true;
} catch (bytes memory err) {
@> if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
// send the coins left
@> wallet.transferRemainder(msg.sender);
return false;
}
}
}
}
contract Coin {
...
function transfer(address dest_, uint256 amount_) external {
uint256 currentBalance = balances[msg.sender];
// transfer only occurs if balance is enough
if (amount_ <= currentBalance) {
balances[msg.sender] -= amount_;
balances[dest_] += amount_;
if (dest_.isContract()) {
// notify contract
@> INotifyable(dest_).notify(amount_);
}
} else {
revert InsufficientBalance(currentBalance, amount_);
}
}
}
contract Wallet {
...
function donate10(address dest_) external onlyOwner {
// check balance left
if (coin.balances(address(this)) < 10) {
revert NotEnoughBalance();
} else {
// donate 10 coins
coin.transfer(dest_, 10);
}
}
function transferRemainder(address dest_) external onlyOwner {
// transfer balance left
coin.transfer(dest_, coin.balances(address(this)));
}
...
}
interface INotifyable {
function notify(uint256 amount) external;
}
GatekeeperThree
- Description: To deal with this
GatekeeperThree
contract, we will have knowledge in some terms such asLow level function
,How EVM storage works
. IngateOne
check, we need to create an EOA account and use it to callGatekeeperThree::construct0r
to beGatekeeperThree::owner
, after that we just call theGatekeeperThree::enter
function by this EOA. Next one, we have to callGatekeeperThree::createTrick
to createSimpleTrick
contract andGatekeeperThree::getAllowance
function with a password which is read inslot 2
ofSimpleTrick
contract's storage. Easily withgateThree
, we send an amount ether larger than0.001 ether
toGatekeeperThree
.
Switch
- Description: As we can see in
Switch
contract, theonlyOff
modifier require the to copy the message datamsg.data
from position 68 with the length is 4 and compare it with theSwitch::offSelector
value. It means that we will call theSwitch::flipSwitch
function with_data
argument which is equaloffSelector
in the samebytes4
type. If it is succeed, the contract will call itself with_data
. So, we can pass the encoding function as the end of_data
. offSelector
= 0x20606e15abi.encodeFunctionSignature("turnSwitchOn()")
= 0x76227e12_data
= 0x30c13ade0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000020606e1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000476227e1200000000000000000000000000000000000000000000000000000000 => We just send the transaction with the data is the above value.await sendTransaction({from: player, to: contract.address, data:"0x30c13ade0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000020606e1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000476227e1200000000000000000000000000000000000000000000000000000000"}}
HigherOrder
- Description: The goal of
HigherOrder
contract is that we become acommander
. To becomecommander
, we have to callHigherOrder::claimLeadership
function with bypass the(treasury > 255)
condition. Therefore, we need to settreasury
value larger than255
(uint256). TheregisterTreasury
function provide the yul code block which is able to setcalldataload(4)
totreasury
storage slot. Thecalldataload(4)
will load the bytes from position 8 (4 bytes = 8 bits) to position 8 + 32. Because of it, we will make the bytes data for callingregisterTreasury()
function with bytes data value of256
(256 > 255) applying from position 8 to position 8 + 32.
256
= 0x100 (Hex) = 0x0000000000000000000000000000000000000000000000000000000000000100 (Bytes32)
registerTreasury(uint8)
= 0x211c85ab
=> data
= "0x211c85ab000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
Send Transaction Command: await sendTransaction({from: player, to contract.address, data:"0x211c85ab000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"})
Stake
- Description: The vulnerability of
Stake
contract is that calling the functions of others call bycall
which is a low level function, in this case that it's callingallowance
andtransferFrom
function inWETH
contract. Why is it vulnerable? Because it will return the boolean variable of the state of action, it mean that it will return true if successfully and false if vice verse, it will not revert transactions when failed.