Fallback
- Description: The
Fallbackcontract 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
Falloutcontract is lower than0.8.0, i can not implement test in there. Just callFallout::Fal1out()` function to reclaim the ownership contract.
Coin Flip
-
Description:
CoinFlipcontract use the random side of coin byblock.number. -
Recommended: Using VRF instead.
Telephone
- Description:
tx.originandmsg.sendershould be different if we use the other contract to callTelephonecontract.
Token
-
Description: Underflow / Overflow in version under
0.8.0. -
Recommended: Using
^0.8.0version orSafe Mathlibrary.
Delegation
- Description: Careful of using delegate call.
Force
- Description:
Forcecontract 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::passwordis private so we can not read it. However, everything in blockchain is public, we will get it from storage ofVaultcontract. -
Proof of Code: There are 2 variable with
boolandbytes32type storing at slot 0 and slot 1 in contract's memory becausebooltype is 32 bytes in slot 0 andbytes32is in slot 1 (each slot has 32 bytes itself).
bool public locked;
bytes32 private password;King
- Description:
Kingcontract is competitive contract with thekingwill 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 lastprizeandKingcontract will send back lastkingtheir ether. What happen if lastkingis a contract without fallback or receive methods? The lastkingwill be the king forever.
Re-entrancy
- Description: This
Reentrancecontract allows us to re-enter thewithdrawfunction through other contract has receive or fallback methods containingReentrance::withdrawcall because theReentrance::donatewill send ether to the callee. The reason is that theReentrancecontract change the user's balance state after the invoker finished.
Elevator
- Description: Attack the
Elevatorcontract by implementing the other contract with customisLastFloorfunction.
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
ERC20standard ofOpenzeppelinand override thetransferfunction to apply the duration of time. However, they do not override thetransferFromfunction with the same functionality withtransfer.
Preservation
- Description:
Preservationcontract use thedelegatecallto call the libraries to set itsstoredTime. To use thedelegatecallthe storage of callee must be match with the storage of caller. The position ofstoredTimeinLibraryContractisslot0but inPreservationisslot4, thenstoredTimeinLibraryContractmatched withtimeZone1LibraryinPreservation
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:
Recoverycontract is a factory contract to produceSimpleTokenwith corresponding inputs. We cannot know the newSimpleTokencontract address when finished because of not code factory verification but we can compute the created contract address based onsenderandnonce(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^256and the array will expand to entire storage by the arithmetic underflow of array length. Usingretractto expand the array to occupy entire storage and change the value ofslot0byrevisefunction to modify theownervalue 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
withdrawfunction, thepartnerandownerwill be transfer ether from contract. However,partneris withdrawal withcallmethods andowneris used bytransfer. We can prevent the anyone calling thewithdrawfunction by using all the gas limit in withdrawal transaction. To do this, we implement thepartneris the receivable contract havingreceivefallback consume all gas limits.
Shop
- Description: The
Shopcontract is dependent on thepricefunction ofmsg.sender. So we just make other contract havingpricefunction return the value based onShop::isSoldvariable
DEX
- Description: The
Dexcontract hasswapfunction with the price calculated byamountOfSwapandbalanceof token1 of contract andbalanceof 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
swapfunction does not have any check thefromandtotoken address, the malicious user can pass their virus token address tofromandtoaddress 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
underlyingtoken (DoubleEntryPoint) stuck inCryptoVaultby callingCryptoVault::sweepTokenfunction with an argument asLegacyTokentoken. The reason is thatLegacyTokencontract has a customizable weirdtransferfunction which will call theDoubleEntryPoint::transferifLegacyToken::delegateis set. - To protect this vulnerability, we recommend the
DetectionBotcontract which will detect whenDoubleEntryPoint::delegateTransferis called. - The
DetectionBotcontract will compare theorigSenderwithCryptoVaultcontract address. If equation, will callForta::raiseAlert.
- The malicious user can drain all the
Motorbike
- Description:
- The
Motorbikecontract is proxy contract to delegatedelegatecalltoEnginecontract as an implementation contract. Usingselfdestructto break theEnginecontract, we have to takeover theEnginecontract to be able to callupgradeToAndCallfunction. We will deploy aHackcontract to be an new implementation contract with ahackfunction containingselfdestruct. To takeover theEnginecontract, we have to callinitializefunction to setupgraderto our wallet and callupgradeToAndCallwith arguments asHackcontract address and signature ofhackfunction.
- 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:ownerandpendingAdminvariable is stored in same slot 0. So we can setpendingAdminto be able to changeowner - Call
addToWhitelistfunction to becomewhitelisted - Drain all contract's balance, we can drain all the balance because
multicallfunction accept re-entry it to calldeposit2 times with only0.001 ether. - Call
setMaxBalancefunction withmsg.senderas an argument casted touint256.
- Become
Good Samaritan
-
Description: The
GoodSamaritan::requestDonationfunction will be call by anyone if they need some tokens. However, this function is usingtry catchto check the succeed ofWallet::donate10function 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 thenotifyfunction of ourHackcontract. -
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
GatekeeperThreecontract, we will have knowledge in some terms such asLow level function,How EVM storage works. IngateOnecheck, we need to create an EOA account and use it to callGatekeeperThree::construct0rto beGatekeeperThree::owner, after that we just call theGatekeeperThree::enterfunction by this EOA. Next one, we have to callGatekeeperThree::createTrickto createSimpleTrickcontract andGatekeeperThree::getAllowancefunction with a password which is read inslot 2ofSimpleTrickcontract's storage. Easily withgateThree, we send an amount ether larger than0.001 ethertoGatekeeperThree.
Switch
- Description: As we can see in
Switchcontract, theonlyOffmodifier require the to copy the message datamsg.datafrom position 68 with the length is 4 and compare it with theSwitch::offSelectorvalue. It means that we will call theSwitch::flipSwitchfunction with_dataargument which is equaloffSelectorin the samebytes4type. 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
HigherOrdercontract is that we become acommander. To becomecommander, we have to callHigherOrder::claimLeadershipfunction with bypass the(treasury > 255)condition. Therefore, we need to settreasuryvalue larger than255(uint256). TheregisterTreasuryfunction provide the yul code block which is able to setcalldataload(4)totreasurystorage 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
Stakecontract is that calling the functions of others call bycallwhich is a low level function, in this case that it's callingallowanceandtransferFromfunction inWETHcontract. 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.