To complete this challenge, we must make this contract balance at least one ether. This contract provides us with buy() and sell() functions where the buy() function contract checks if the amount of tokens we are buying * 10^18 is equal to msg. Value, since this contract is not using the safemath library, we can overflow the integers. We can pass a value to numbTokens such that when multiplied with PRICE_PER_TOKEN, the product will be more excellent than 2^256. This makes the integer overflow and gets a mod of 2^256 done. The value will be the remainder which is less than an ether which we give as the msg.value to get the check pass and get tokens. Now we have many tokens with significantly lower prices, and we can sell them and drain the contract’s balance to complete this challenge.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from brownie import TokenSaleChallenge, accounts, Wei from Crypto.Util.number import inverse
defmain(): deployer = accounts[0] player = accounts[1]
We must make the player balance greater than 1000000 to complete this challenge. Similar to the previous challenge, this contract is not using the safemath library. However, in this case, instead of an overflowing integer, we can make an underflow of the player’s balance. Suppose we observe the transferFrom() function. In that case, it checks whether the from address has sufficient balance and the allowance of the from to msg.sender, but the transfer is done from msg.sender to the given to_address since it is calling the _transfer() function and that function is transferring funds from msg.sender so, in this case, the contract does not check for the balance of the msg.sender before deducting it. Hence, if we send more funds than the balance from our player account using the transferFrom() function, then we can make the integer which holds the player’s balance get underflowed and make the balance more significant than 1000000 to complete this challenge.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
from brownie import TokenWhaleChallenge, accounts, ZERO_ADDRESS
defmain(): deployer = accounts[0] player = accounts[1] helper = accounts[2]
This retirement fund is what economists call a commitment device. I’m trying to make sure I hold on to 1 ether for retirement.
I’ve committed 1 ether to the contract below, and I won’t withdraw it until 10 years have passed. If I do withdraw early, 10% of my ether goes to the beneficiary (you!).
I really don’t want you to have 0.1 of my ether, so I’m resolved to leave those funds alone until 10 years from now. Good luck!
// an early withdrawal occurred require(withdrawn > 0);
// penalty is what's left msg.sender.transfer(address(this).balance); } }
To complete this challenge, we need to steal the funds of this contract. This contract allows us to collect the penalty using the collectPenalty() function if the owner withdraws his funds before expiration. However, suppose we observe the collectPenalty() function. In that case, the contract checks the difference between the start balance, which is the amount deposited in the start, and the current balance is 0. Suppose the remaining balance is kept from our address. So if we send some extra ether to this contract, we steal all the balance left inside this contract, but this contract does not have a fallback() or receive () function to accept ether. However, we can force this contract to receive ether by destroying another contract using the selfdestruct method and to send it is remaining balance to this contract.
To complete this challenge, we need to make the bool isComplete to true. Since we can change the length of the map, which is an uint256[], we can update the length to 2^256 so we can get access to all the slots inside this contract’s storage. Now we can calculate the index number to which it points to the slot 0 where the isComplete is in and overwrite it with 0x01; hence the value of isComplete is true.
1 2 3 4 5 6 7 8 9 10 11 12 13
from brownie import MappingChallenge, accounts, web3
defmain(): deployer = accounts[0] player = accounts[1]
functiondonate(uint256 etherAmount) public payable { // amount is in ether, but msg.value is in wei uint256 scale = 10**18 * 1 ether; require(msg.value == etherAmount / scale);
functionwithdraw() public { require(msg.sender == owner); msg.sender.transfer(address(this).balance); } }
To complete this challenge, we need to steal the funds of this contract. We can steal the funds only if we are the owner since the function donate() creates an uninitialized storage pointer, which will be pointing to slot 0 by default. We the donation.timestamp overwrites the length of the donations array, and the donation.etherAmount will overwrite the owner variable. Now there is another bug in this contract in the calculation of the scale, which makes us easier to give the value of our address to the function to become the owner. Since the scale is 10^36, we can send the value of our address divided by 10^36 and send the answer amount of ether and pass our address to the donate() function to become owner and we can steal the funds by calling withdraw() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from brownie import DonationChallenge, accounts
defmain(): deployer = accounts[0] player = accounts[1]
This contract locks away ether. The initial ether is locked away until 50 years has passed, and subsequent contributions are locked until even later.
All you have to do to complete this challenge is wait 50 years and withdraw the ether. If you’re not that patient, you’ll need to combine several techniques to hack this contract.
contract FiftyYearsChallenge { struct Contribution { uint256 amount; uint256 unlockTimestamp; } Contribution[] public queue; uint256 public head;
address public owner;
functionFiftyYearsChallenge(address player) public payable { require(msg.value == 1 ether);
owner = player; queue.push(Contribution(msg.value, now + 50 years)); }
functionisComplete() public view returns (bool) { returnaddress(this).balance == 0; }
functionupsert(uint256 index, uint256 timestamp) public payable { require(msg.sender == owner);
if (index >= head && index < queue.length) { // Update existing contribution amount without updating timestamp. Contribution storage contribution = queue[index]; contribution.amount += msg.value; } else { // Append a new contribution. Require that each contribution unlock // at least 1 day after the previous one. require( timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days );
functionwithdraw(uint256 index) public { require(msg.sender == owner); require(now >= queue[index].unlockTimestamp);
// Withdraw this and any earlier contributions. uint256 total = 0; for (uint256 i = head; i <= index; i++) { total += queue[i].amount;
// Reclaim storage. delete queue[i]; }
// Move the head of the queue forward so we don't have to loop over // already-withdrawn contributions. head = index + 1;
msg.sender.transfer(total); } }
We aim to steal all the funds from this contract locked for 50 years. If we look at the upsert() function, first, it checks if the index is inside the bounds of the queue array. If yes, then it just adds the msg.value to the amount of that struct at the index. Suppose the index is out of bounds. In that case, it checks for the timestamp to be greater or equal to the last timestamp and creates a struct, and pushes it to the queue since there is no keyword memory used while initializing the struct, it points to the address 0 that is contribute.amount points to queue.length and contribute.unlockTimestamp points to head. Now the exploit idea is to push a contribution to the queue with the timestamp 0 by overflowing the check inside the upsert() function and force some ether such that it has the actual amount to transfer in withdraw() function and execute the withdraw() function to steal all the funds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from brownie import FiftyYearsChallenge, Rf_Exploit, accounts
defmain(): deployer = accounts[0] player = accounts[1]
This contract can only be used by me (smarx). I don’t trust myself to remember my private key, so I’ve made it so whatever address I’m using in the future will work:
I always use a wallet contract that returns “smarx” if you ask its name. Everything I write has bad code in it, so my address always includes the hex string badc0de. To complete this challenge, steal my identity!
functionisBadCode(address _addr) internal pure returns (bool) { bytes20 addr = bytes20(_addr); bytes20 id = hex"000000000000000000000000000000000badc0de"; bytes20 mask = hex"000000000000000000000000000000000fffffff";
for (uint256 i = 0; i < 34; i++) { if (addr & mask == id) { returntrue; } mask <<= 4; id <<= 4; }
returnfalse; } }
To solve this challenge, we need to authenticate this contract with a contract with 'badc0de' in its address’s hex and return’s 'smarx' for when function name() is called. We can deploy a contract that returns the name but having 'badc0de' inside the address is something we can only achieve by bruteforce. So we need to brute for an address and a nonce which gives us the required address when we deploy the contract. After much brute force got an address inside the console, so I used that address.
Recall that an address is the last 20 bytes of the keccak-256 hash of the address’s public key.
To complete this challenge, find the public key for the owner’s account.
1 2 3 4 5 6 7 8 9 10 11 12
pragma solidity ^0.4.21;
contract PublicKeyChallenge { address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31; bool public isComplete;
functionauthenticate(bytes publicKey) public { require(address(keccak256(publicKey)) == owner);
isComplete = true; } }
To solve this challenge, we must find the PublicKey of the given address. Since the address is the last 20 bytes of the public key’s keccak hash, we cannot reverse the hash to obtain publickey. However, there is another way to find the public key if the address has made a transaction. Since the transaction contains the transaction’s signature and the public key, we can get the public key if the address made any transaction.
Looking at the ropsten.etherscan.io gives us four transactions made from other addresses to this address, and this address made one transaction with data Thanks, man!
here’s the raw transaction: 0xf87080843b9aca0083015f90946b477781b0e68031109f21887e6b5afeaaeb002b808c5468616e6b732c206d616e2129a0a5522718c0f95dde27f0827f55de836342ceda594d20458523dd71a539d52ad7a05710e64311d481764b5ae8ca691b05d14054782c7d489f3511a7abf2f5078962
from brownie import PublicKeyChallenge, accounts from rlp import encode, decode from ethereum.transactions import Transaction from fastecdsa.curve import secp256k1 from fastecdsa.point import Point from sympy import sqrt_mod from gmpy2 import invert from eth_utils import keccak, to_checksum_address
defrecover_publicKey(raw_tx, address): tx = decode(bytes.fromhex(raw_tx[2:]), Transaction) # print(tx.to_dict()) # ''' # {'nonce': 0, # 'gasprice': 1000000000, # 'startgas': 90000, # 'to': '0x6b477781b0e68031109f21887e6b5afeaaeb002b', # 'value': 0, # 'data': '0x5468616e6b732c206d616e21', # 'v': 41, # for some reason here chain id should is not 3 # 'r': 74776771311019569939017621593480679160618399812524181808306514788568607828695, # 's': 39381076589634547203973423246354256320472887426210737547826636053693505964386, # 'sender': '0x92b28647ae1f3264661f72fb2eb9625a89d88a31', # 'hash': '0xabc467bedd1d17462fcc7942d0af7874d6f8bdefee2b299c9168a216d3ff0edb'} # ''' new_tx = Transaction(tx.nonce, tx.gasprice, tx.startgas, tx.to, tx.value, tx.data, 3) r, s, a, b, p, q, G = tx.r, tx.s, secp256k1.a, secp256k1.b, secp256k1.p, secp256k1.q, secp256k1.G data = Transaction.serialize(new_tx) z = int(keccak(encode(data)).hex(), 16) % p y = sqrt_mod((r**3 + a*r + b) % p, p, all_roots=True) R = Point(r, y[0], secp256k1) R_ = Point(r, y[1], secp256k1) ri = int(invert(r, q)) # s = k^-1 * ( h + r*d ) -> s*R = ( h + r*d )*k^-1*k*G = ( h+ r*d )*G # -> (h*G + r*d*G) -> s*R - zG = (h*G + r*d*G - h*G ) = r*d*G -> (s*R - zG)*ri = r*d*G*(r^-1) -> -> d*G k = ri * (s*R - z*G) k_ = ri * (s*R_ - z*G) addr = keccak(bytes.fromhex(hex(k.x)[2:]+hex(k.y)[2:]))[-20:].hex() addr_ = keccak(bytes.fromhex(hex(k_.x)[2:]+hex(k_.y)[2:]))[-20:].hex() assert address in [addr, addr_], f'address not found!!'
# found publickey publicKey = [k if addr == address else k_][0]
return publicKey
defmain(): deployer = accounts[0] player = accounts[1]
# ropsten is depreciated, so i got this from other writeups online. raw_tx = '0xf87080843b9aca0083015f90946b477781b0e68031109f21887e6b5afeaaeb002b808c5468616e6b732c206d616e2129a0a5522718c0f95dde27f0827f55de836342ceda594d20458523dd71a539d52ad7a05710e64311d481764b5ae8ca691b05d14054782c7d489f3511a7abf2f5078962'
publickey = recover_publicKey( raw_tx, '92b28647ae1f3264661f72fb2eb9625a89d88a31') # after finding the publickey
To complete this challenge, send a transaction from the owner’s account.
1 2 3 4 5 6 7 8 9 10 11 12
pragma solidity ^0.4.21;
contract AccountTakeoverChallenge { address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b; bool public isComplete;
functionauthenticate() public { require(msg.sender == owner);
isComplete = true; } }
This is a similar challenge to the previous one, but we need to overtake the account by recovering the private key. I had no clue how is that possible until I looked at the etherscan for transactions and found two transactions with the same nonce that is, they both have the same r-value, which makes it very easy for us to crack the private key.
from brownie import AccountTakeoverChallenge, accounts from rlp import encode from ethereum.transactions import Transaction from fastecdsa.curve import secp256k1 from eth_utils import keccak from gmpy2 import invert
defcalculate_hsh(tx): new_tx = Transaction(tx['nonce'], tx['gasPrice'], tx['gasLimit'], tx['to'], tx['value'], tx['data'], tx['v']) data = Transaction.serialize(new_tx) z = int(keccak(encode(data)).hex(), 16) return z
# r and s values from the writeups r = 0x69a726edfb4b802cbf267d5fd1dabcea39d3d7b4bf62b9eeaeba387606167166 s1 = 0x7724cedeb923f374bef4e05c97426a918123cc4fec7b07903839f12517e1b3c8 s2 = 0x2bbd9c2a6285c2b43e728b17bda36a81653dd5f4612a2e0aefdb48043c5108de p = secp256k1.q
definv(x, p): returnint(invert(x, p))
k = ((z1 - z2) * inv(s1 - s2, p)) % p d = ((k*s1 - z1) * inv(r, p)) % p assert ((k*s2 - z2) * inv(r, p)) % p == d
player = accounts.add(private_key=d) assert player.address == '0x6B477781b0e68031109f21887e6B5afEAaEB002b', player.address contract.authenticate({'from': player})
assert contract.isComplete() == True
Miscellaneous
Asume ownership
To complete this challenge, become the owner.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
pragma solidity ^0.4.21;
contract AssumeOwnershipChallenge { address owner; bool public isComplete;
functionAssumeOwmershipChallenge() public { owner = msg.sender; }
functionauthenticate() public { require(msg.sender == owner);
isComplete = true; } }
To complete this challenge, we need to become owner. If we observe the constructor of this contract, it contains a typo, so it does not count as a constructor anymore, and it is a public function that everyone can call, and this function sets the owner. So we can call the AssumeOwmershipChallenge() function to become the owner.
1 2 3 4 5 6 7 8 9 10 11 12 13
from brownie import AssumeOwnershipChallenge, accounts
defmain(): deployer = accounts[0] player = accounts[1]
I created a token bank. It allows anyone to deposit tokens by transferring them to the bank and then to withdraw those tokens later. It uses ERC 223 to accept the incoming tokens.
The bank deploys a token called “Simple ERC223 Token” and assigns half the tokens to me and half to you. You win this challenge if you can empty the bank.
contract TokenBankChallenge { SimpleERC223Token public token; mapping(address => uint256) public balanceOf;
functionTokenBankChallenge(address player) public { token = newSimpleERC223Token();
// Divide up the 1,000,000 tokens, which are all initially assigned to // the token contract's creator (this contract). balanceOf[msg.sender] = 500000 * 10**18; // half for me balanceOf[player] = 500000 * 10**18; // half for you }
To complete this challenge, we need to steal the balance. If we observe here that we have two contracts, SimpleERC223Token and TokenBankChallenge, and two kinds of balances, one inside each contract, we need to steal the balance inside the SimpleERC223Token. We can understand that the challenge contract contains the full balance inside the token contract and shares the balance inside it with the owner and player. Let us look at the transfer function inside the token contract. It calls a tokenFallback() function if the to_address is a contract. Now, if we observe the challenge contract inside the withdraw function, it calls the token.transfer() and then deducts our balance. It indirectly calls tokenFallback() function. so if we again call the withdraw() function inside our tokenFallback() function of our contract, we can make the contract transfer the amount again since the token.transfer() activates tokenFallback() and then withdraw() function and this all happens before the deduction of our balance, so our balance stays the same even if we got our funds transferred. This is a re-entrancy attack, and we can call the withdraw() function until we drain the contract’s balance. So, in this case, we call withdraw() 2 times, manually and once inside the tokenFallback() function, and we get all the funds to our address; hence we complete the challenge.