Ikhlas Mohammad Profile picture
Jul 7, 2022 207 tweets >60 min read Read on X
[31/236]

So why did this transaction have a MetaMask pop up and why is this the only function interaction that has been registered on Etherscan. To understand it better let us look at "stateMutability" witin the ABI (docs.soliditylang.org/en/v0.8.13/abi…) Image
[32/236]
As per the ABI, the state mutability has been pure or view for the other functions. These do not affect the Blockchain. While on the other "authenticate()" function has a state mutability as nonpayable which does affect the blockchain as data is being registered. Image
[33/236]
Back to the transaction, once the approve button is clicked on the Metamask pop up, a transaction hash is generated and broadcast to the network , it is sent to a transaction pool. Image
[34/236] Once a miner has picked up the transaction, included in a block and it is confirmed then the "Mined transaction" appears.

The object displayed has the same information that can be checked on the Etherscan using the Transaction hash. Image
[35/236] Now click on "Submit Instance" and a new MetaMask pop appears.

This time the interaction is with the Ethernaut Contract (ending in C33). ImageImage
[36/236]
Level 0 has been completed!

Source code of the Contract. ImageImage
[37/236] Okay, let us move to the next step, Click on "Go to Next Level" and "Get New Instance".

LEVEL 1 - FALLBACK

The source code has been provided, do note that it is based on Solidity 0.6 and it does have a SafeMath library. Image
[38/236] There have been changes in newer Solidity version.

Alright, let us look at the task.

You will beat this level if
1️⃣ you claim ownership of the contract
2️⃣ you reduce its balance to 0
[39/236] The hints provided are:

Things that might help
🔸 How to send ether when interacting with an ABI
🔸 How to send ether outside of the ABI
🔸 Converting to and from wei/ether units (see help() command)
🔸 Fallback methods
[40/236] So basically need to take over the contract by becoming an owner and then to run the withdraw function to remove the funds.

The constructor, which is run only once, at the time of contract deployment, it specifies the owner address field as msg.sender.
[41/236] msg.sender in this case would be the address deploying the contract. The constructor also has a contribution mapping that maps the address of msg.sender with 1000 ETH.
[42/236] The modifier "onlyOwner" requires that msg.sender is the owner. Some functions including withdraw has an onlyOwner modifier which means that only the owner of the contract (who was specified in the constructor) can carry out the withdraw function.
[43/236] To understand the challenge better, let us define fallback and receive function.

🔸 receive() external payable — is for any empty calldata (and any value)
🔸 fallback() external payable (optional) — when no other function matches (including the receive function)
[44/236] receive()

A contract is only allowed to have a single receive function, it does not have the word function with it, it is external, payable, does not have any arguments, does not return anything.

Calls can be made via send() or transfer()
[45/236] fallback()

A contract is only allowed to have a single fallback function, it does not have the word function with it, it is external, does not have any arguments, nor it returns anything.

It does receive data and can receive Ether (in that case it should be payable).
[46/236] More information regarding receive and fallback (docs.soliditylang.org/en/v0.8.12/con…)

This contract only has a receive function, let us focus on that. Image
[47/236] The receive function has a require statement that says:

1️⃣ msg.value > 0 (basically the amount of Ether being sent)
2️⃣ contributions[msg.sender] > 0 (the msg.sender, us, need to have a contributions balance greater than 0)
[48/236] Then owner = msg.sender, this means that then the msg.sender would become owner.

And once we become the owner, we can execute the withdraw function.

Alright so first we need to have contributions balance.
[49/236]

The contribute function requires msg.value to be less than 0.001 ether.

Let us check our initial Contributions balance. This can be done using getContribution() function. Output is zero. ImageImage
[51/236] Now that we do have a contributions balance let us call the receive function! The receive function is also being called by the same amount of 0.0005 eth.

The transaction has gone through, let us check who the owner is now..... ImageImage
[52/236]
So the player is now the contract owner!

The only thing left is to withdraw.

Let us run the withdraw function. Notice within the "TO" line, an amount of 0.001 eth is being transferred from the contract to the player (that is the amount that was put in) ImageImageImage
[53/236] That was it for Level 1! Submit the instance and move on to the next level!

LEVEL 2 - FALLOUT

Task: Claim ownership of the contract below to complete this level.

Hints provided: Things that might help
🔸 Solidity Remix IDE Image
[54/236] Basically the target of this level is to become contract owner.

Let us check the current owner.

Owner address is currently address 0.

Let us read through the contract and see where the owner address is being specified. ImageImage
[55/236] It looks pretty straight forward, the function Fal1out does not have any modifiers or require statement restricting access.

Also to be noted is that in older versions of solidity, the constructor could be the same name as Contract...
[56/236] but starting from 0.5 the constructor keyword is required (docs.soliditylang.org/en/v0.6.0/050-…).

In Solidity 0.6 (version of the contract), a function is not allowed to have the same name as the contract, contract would not compile.
[57/236] So this would mean that the function Fal1out is not the same as the contract name, Fallout.

Noticed something? The second 'l' is a '1'! So it is simply a regular function that anyone can call.
[58/236] Let us call the Fal1out Function.

The transaction goes through and now the contract owner is player! ImageImage
[59/236] Do note that no funds were sent even though it is a payable function as there was no requirement or check for it, none were sent.

The only thing left to do is submit the instance and go to next level!
[60/236] LEVEL 3 - COINFLIP

Task - need to guess the correct outcome of coinflip 10 times in a row

The source code: Image
[61/236] Let us look at the code more in depth.

The constructor initializes the consecutiveWins and sets it to 0 which would be the counter for the number of wins.

There is only 1 function with in the contract, flip. It takes in 1 boolean input ( true or false).
[62/236] The blockValue variable is equivalent to the previous block hash (more details here docs.soliditylang.org/en/v0.8.15/uni…)

The next if statement, blockValue is equal to lasthash then to revert, this bascially means that in each block the function can only be run once.
[63/236] The coinFlip variable then divides the blockValue variable with a very long fixed integer, FACTOR.

The value of coinFlip is compared to the player's input, if they match then the player has won the flip else lost.
[64/236] In order to ensure that the consecutive coin flips are correct 10 times in a row, it cannot be done based on trial and error.

Instead another contract needs to be written that would do the calculation of the result and accordingly call the coinFlip..
[65/236] ...contract to ensure it would win.

This is the contract that was written to call the coinFlip contract.

It is quite similar to the coinFlip, let us go over the changes. Image
[66/236] Firstly the import contract refers to the same SafeMath library but it has been made to specify the correct version that is supported by solidity 0.6 rather than the latest which does not support it.
[67/236] The function name has been changed to tester() and it does not require any user input.

tester() calculates the value of blockValue and then coinFlip.

There is no comparison and it passes on the desired / winning bool (side) to the coinFlip contract.
[68/236] The coinFlip contract address is specified in the contract for it to communicate accordingly.

The whole set up has been done in Remix (remix.ethereum.org) as it makes it possible to write the contract and call another contract.
[69/236] Instance Contract Address and initial count of winning is zero. Image
[70/236]
Remix Set up.

Running the tester() function.

Consecutive win count goes up from 1 to 2.

Repeating the steps until 10 is reached. ImageImageImage
[71/236] And that is it! Submit the instance! Level completed! Image
[72/236] LEVEL 4 - TELEPHONE

Task: Claim ownership of the contract to complete this level. Image
[73/236] Telephone contract has a constructor that makes the msg.sender the owner.

There is one function changeOwner that does not have any modifier or restrictions - anyone can access the function. It has 1 input an address.
[74/236] There is an if condition that says if the tx.origin is not the msg.sender then the address that has been put in would become the owner.

If this can be done, then the task would be completed.

First, let us understand what is tx.origin.
[75/236] tx.origin refers to the EOA (External Owned Addresses) that created the transaction.

Ethereum has 2 types of accounts EOA (the ones all individuals have) and Contract Addresses (the one controlled by code - Smart Contracts) - (ethereum.org/en/developers/…)
[76/236] msg.sender refers to the last instance from where it is called. msg.sender can be either an EOA or a Smart Contract.

When an EOA sends transaction to a Contract X, the Contract X refers to the address of EOA as msg.sender.
[77/236] If Contract X calls Contract Y with the same transaction then Contract Y would refer to Contract X as msg.sender.

So how can we use this to our advantage?

Let us call the Contract Telephone from another Contract.
[78/236] In this case the tx.origin would be the player and msg.sender would be the other Contract.

As they will not be the same, the changeOwner function can be run successfully!

More info - blockchain-academy.hs-mittweida.de/courses/solidi… Image
[79/236]
The InterContract calls the Telephone Contract and passes in the player address to changeOwner function. Image
[80/236] The owner address before an after running the Inter function in the InterContract.

Mission completed. Submit instance. On to the next level! ImageImage
[81/236] LEVEL 5 - TOKEN

Task - You are given 20 tokens to start with. To pass the level you need to get your hands on any additional tokens. Preferably a very large amount of tokens.

Hint - What is an odometer? Image
[82/236] The Token Contract has a Constructor that sets the initial supply, which would be equal to the total supply and also that balance of the msg.sender deploying the contract.

There are 2 functions, balanceof to check the balance of an address and a transfer function. Image
[83/236] The transfer() requires an address input and a value input.

There is a require statement that checks that the balance of msg.sender is greater than or equal to the input value (or transfer amount).
[84/236] If it passes through the balance of msg.sender is reduced and the balance of receiving address is increased.

The hint provided about odometer gives a hint about the overflow and underflow, it refers to analog odometers that would reset to 00000 after going to 99999.
[85/236] Note that this is Solidity 0.6 and SafeMath library is also not imported.

So the point of attack has to be at the require statement within the transfer function such that it would allow the transfer of funds greater than the balance.
[86/236] In other words, it should give a balance greater than 0 when taking the difference between balance and input value.

In this case we need an underflow.
[87/236] As these are uint (unsigned integers no negative), if 1 is subtracted from 0 it would go to max of the value (so for uint8 it would be 255) (medium.com/3-min-blockcha…)
[88/236] In order to trigger this we need to reduce the balance to 0, let us transfer the available balance to "level account" which has all the balance. Image
[89/236]
Now that the balance has been made to 0, let us run the transfer function and trigger an underflow. Image
[90/236]
Select a random address as the receiving address, do not put in the player as it would not work.

In the value input, use "0+1". Note that this could have been done with the 20 tokens itself without reducing the balance to 0 as well. Image
[91/236] Current balance is 0 - (0+1) would trigger the underflow. This would result the value to be uint max. This would pass the require statement and also under flow the balance of player.
[92/236] Note that the total supply remains the same only the balance records have been manipulated.
Task completed! On to the next level! Image
[93/236] LEVEL 6 - DELEGATION

Task - claim ownership of the instance.

Hint - Look into delegatecall low level function, fall back methods and method ids Image
[94/236] Let us have a look at the contract, there are 2 contracts - Delegate and Delegation.

The instance gives access to the Delegation Contract. There is no direct access to Delegate Contract.
[95/236] Delegation has a constructor that stores the delegate Contract address and stores the msg.sender as the owner.

There is only a fallback and no other function.
[96/236] The fallback is not payable.

It has a delegatecall that calls the delegate call and the call data is msg.data.
[97/236] Now moving on to the delegate contract, it has a constructor that specifies the owner.

It also has a function pwn() that sets the owner as msg.sender.

This is the function that needs to be delegate called to get the ownership.
[98/236] To make it clear, let us take a step back and understand what is the difference between call and delegatecall.

The first row in the image is call and the second row is a delegatecall. Image
[99/236] Let us look at the call closely, an EOA calls Caller Contract which in turn calls a Target Contract.

When the EOA calls the Caller Contract, the msg.sender and msg.value corresponds to the EOA.
[100/236] When the Caller Contract calls the Target Contract, the msg.sender refers to the Caller Contract Address and msg.value refers to the Caller Contract value.

In the second scenario, an EOA calls the Caller Contract which in turn delegatecalls a Target Contract.
[101/236] When the EOA calls the Caller Contract, the msg.sender and msg.value corresponds to the EOA.

When the Caller Contract delegatecalls the Target Contract, the msg.sender continues to refer to the EOA address and also the msg.value continues to refer to the EOA value.
[102/236] Also it is important point to note that when Caller Contract delegatecalls the Target Contract,

🔸 The state variables of Caller Contract are read and written
🔸 The state variables of Target Contract are not read or written
[103/236] What does this mean in the scenario of the challenge, when the Delegation Contract delegatecalls the Delegate Contract, the variables and states of the Delegation Contract are read and written into.
[104/236] Still not clear? well, when the Delegation Contract would delegatecall the Delegate Contract and pass in the calldata for the pwn() then the owner variable within the Delegation Contract will get changed to msg.sender and not in the Delegate Contract.
[105/236] This would give the player the ownership of the contract and complete the task.

This gives a good explanation about the delegatecalls - medium.com/coinmonks/dele…

This gives a much deeper dive into delegatecall, eip2535diamonds.substack.com/p/understandin…
[106/236] This one could be solved from the console, I chose to do it using Remix ID.

This is the original code, deployed at the instance address. Image
[107/236] First need to get the calldata. Yes it is simply "pwn()"...but to pass it as msg.data, need to convert into the method ID.

Method ID is derived as the first 4 bytes of keccak hash of the the function signature (more info here docs.soliditylang.org/en/v0.8.13/abi…)
[108/236] There would be multiple ways to retrieve the Method ID for pwn()

🔸 An online encoder for function signature (piyolab.github.io/playground/eth…)
🔸 Using web3 library on the console (web3js.readthedocs.io/en/v1.2.11/web…)
[109/236] Both would give the same result "0xdd365b8b"

This is sent as calldata to the Delegation Contract. The fallback function would be activated and then the delegatecall carried out. ImageImage
[110/236] Initial transaction got rejected, on closer look it was due to "an out of gas" error.

The gas had been auto set by metamask but as it was not sufficient, increased it manually when the popup appears to approve the transaction. ImageImage
[111/236] Changed the gas limit to 80,000.

Then the transaction went through.

That is it, the owner of the contract changed to player. Task completed! On to the next level! ImageImage
[112/236] LEVEL 7 - FORCE

Task - make the balance of the contract greater than 0

Hint - use another contract Image
[113/236] The Force contract does not have any functions or even a fallback or receive.

There is no way to interact with the contract directly. Image
[114/236] It is not possible to send the funds directly to the contract.

Solidity has an option to have a self destruct within a contract. (more info - docs.soliditylang.org/en/v0.8.13/int…)
[115/236] Self Destruct requires to specify the address where the available ether needs to be sent.

This can be used to send Ether to Force contract. The Force contract would not be able to revert the transaction as the other contract would have self-destructed.
[116/236] A simple SelfDestruct contract was made with destruct() function that has a self destruct function int it. ImageImage
[117/236] The selfdestruct Contract and destruct() function was run while passing in 1000 wei.

The balance of the Force contract has increased!

Task completed! On to the next challenge! Image
[118/236] LEVEL 8 - VAULT

Task - Unlock the vault Image
[119/236] The Vault Contract has a Constructor with the password being set and vault lock being activated.

There is 1 function, unlock(), that requires a bytes32 input the password.

If the correct password is entered the vault is unlocked.
[120/236]
This is what needs to be done, retrieve the password and run the unlock function.

How to access the password?

Well the password even though it is saved as private variable it is accessible.
[121/236] To understand this better, it is important to understand how is data stored in Solidity.

It is stored in SLOTS of 32 bytes (more details on coinsbench.com/accessing-priv… and )
[122/236] The first variable stored is the locked, it would be at SLOT 0. The information can be viewed using web3 getStorageAt function.

The password is stored in SLOT 1.

This password can then be entered into the unlock function. ImageImage
[123/236] Observe the SLOT 0 now. It has changed from 1 to 0.

Task has been completed, on to the next challenge! Image
[124/236] LEVEL 9 - KING

Task - Game allows anyone to become the "king" by sending in higher than current prize amount. At the time of submission of instance, level will try to reclaim "kingship", prevent it from happening. Image
[125/236] Let us have a look at the Contract. The Constructor sets the msg.sender as the Owner and the King. Also the prize is also set as the msg.value.

There is _king() function to check the current King.
[126/236] There is a receive() function has a require statement that either msg.value is greater than the prize or msg.sender is the owner.

If the condition is met, then previous King receives the msg.value and the current msg.sender becomes the new king.
[127/236] The prize is also updated to match the msg.value.

Now the challenge is to replace the owner as the King and to prevent the owner from becoming the King again. Image
[128/236] So need to think outside the box, the new King does not have to be an EOA, it can be a Contract Address as well.

How can we get the King Contract to be "stuck" or prevent from a new King being placed after changing the king, well let u look at the receive() function.
[129/236] Notice that once a new user has sent the funds higher than prize the old (or current King) receives the msg.value.

We can use that fact, to manipulate, a Smart Contract will not be able to receive funds if it does not have a fallback or receive function.
[130/236] This is exactly what was done in the noReturnNew Contract. There are 2 functions; The accept() function is payable, to accept ether into the contract for gas.

The other function sendKing() is also payable, that calls the King Contract and sends in the msg.value. Image
[131/236] Once the noReturnNew Contract has become the King and instance is submitted, the level is unable to take over the "kingship".

Hence the task is completed! On to the next challenge!

An interesting article about the same bug(kingoftheether.com/postmortem.htm…) Image
[132/236] LEVEL 10 - RE-ENTRANCY

Task - Goal is to steal all the funds from the Contract

Hint - Fallback, revert, using another contract Image
[133/236] Reentrance Contract has 3 Functions and Receive Function is also there.

The donate Function is a payable function with 1 input. The function updates the balance of the input address.

The balanceof function returns the balance of the address fed into the input.
[134/236] The withdraw function has an if statement to check if the available balance of the msg.sender is higher than the amount.

If the statement is true, a call function transfer the amount to msg.sender.
[135/236] Once the transfer is successful then the balance of msg.sender is updated to reflect the withdraw.

The exploit in the contract is that the balance is updated after the transfer.
[136/236] This means if another withdraw is done before the balance is updated then msg.sender would receive twice the withdraw requested amount.

To do this, another Contract, AttackRe, was written.
[137/236] The Reentrance Contract address is stored. The constructor is payable, it has call function to the Reentrance Contract's deposit function with the self contract address as an input. Image
[138/236] There is a withdrawAttack function that calls the Reentrance Contract's withdraw function with the complete amount deposited in the constructor.

There is also a receive() function that has the same withdraw call function as in the withdrawAttack function. Image
[139/236] Now once the AttackRe contract is deployed, the withdrawAttack() function will be run. It would call the Reentrance Contract to withdraw. Once the funds are sent back to AttackRe, it would activate the receive() function which would trigger a loop of withdraw calls.
[140/236] This will continue happening until Reentrancy Contract funds are 0.

Note when I ran this initially I had faced an out of gas error, so I increased the gas limit to 1,000,000. In actual a 130K gas was used (can be seen in the transaction details). ImageImageImage
[141/236] Notice in the transaction history multiple transactions are being carried out and they are all within the same block.

Also the balance before and after can also be seen.
[142/236] More details about such attack here (blog.openzeppelin.com/15-lines-of-co…)

Task of emptying the funds has been completed. On to the next one!
[143/236] LEVEL 11 - ELEVATOR

Task - Reach the Last Floor

Hint - Sometimes solidity is not good at keeping promises; Elevator expects to be used from a Building. Image
[144/236] The source code has an Interface titled Building and a Contract Elevator.

Interfaces are abstract Contracts that has the functions, its inputs types and outputs listed. More details here medium.com/coinmonks/soli…
[145/236] The Elevator Contract has a single function goTo() which takes an integer input. It then records the Building at msg.sender in a building variable.

It then checks whether the lastfloor function with in the building variable returns a false.
[146/236] If it does that then it passes through the condition and sets the top equal to the return of the building's isLastFloor variable.

Confused? Well let me know explain the approach to the solution to make it a bit more clear.
[147/236] In order to the get the top variable as true, it is required that it is required that the isLastFloor function should return false and once the condition has passed through it should return true.

Basically it needs to alternate or toggle.
[148/236] The AttackE Contract inherits from the Building (more details - docs.soliditylang.org/en/develop/con…)

The Contract itself has 2 functions, isLastFloor and callElevatorTop. Image
[149/236] callElevatorTop() function calls the Elevator Contract's function goTo() and passes the input to it.

isLastFloor() has an if condition that checks the mod of count with 2 (calculatorsoup.com/calculators/ma…). If the result is equivalent to 1 then it returns false.
[150/236] Also it increments the counter. In case if the mod result is not equivalent to 1 then it would return true.

Basically, it would alternate between true and false as the count would increase.
[151/236] This would give the desired effect within the goTo function in Elevator Contract (initial if statement to be false and to return true so it gives the top variable as true).

This exercise initially had an outofgas error so a higher limit was used.
[152/236] Once the AttackE contract is deployed, the function callElevatorTop is run. This would call the goTo function within the Elevator function.

This results in top variable being true.

The task is now completed! On to the next one! Image
[153/236] LEVEL 12 - PRIVACY

Task - unlock the contract

Hint - how storage works, parameter parsing, casting Image
[154/236] Privacy contract has a Constructor that takes in bytes32 array of size 3 and stores in data.

There is 1 function, unlock() that takes in a bytes16 input that is checked whether it matches up with part of data stored in the contract.
[155/236] If it matches then locked variable changes to false.

This is what needs to be achieved in this challenge.

Now an important thing to note that private variables stored on the blockchain are easily accessible.
[156/236] Caution needs to be taken what data are stored on the blockchain.

For this task, it is important to understand how data is stored on the blockchain. (more info - coinsbench.com/accessing-priv…)
[157/236] In Solidity the variables are stored in "slots". Each slot can hold upto 32 bytes. Some variables can occupy less. (more info - docs.soliditylang.org/en/v0.8.15/int…)
[158/236] There are multiple ways to access the data and perhaps the web3 approach using getStorageAt would be the obvious choice (more info - web3js.readthedocs.io/en/v1.2.11/web…) but I would like to show another manner that is more visual.
[159/236] In etherscan it is possible to view the storage states.

Let us go to the Contract on etherscan. Over there go to the Internal Transaction and the Contract initiation transaction would be visible. ImageImage
[160/236] On this page need to go to "State". Over here click on the dropdown which has the Privacy Contract Address.

As can be seen there are 6 storage locations or slots mentioned. ImageImageImage
[161/236] Let us look at the first variable, it is a boolean, it takes up 1 byte. Let us click on the After dropdown in Slot 0 and change hex to number. It is showing as 1 now as I have already completed the task (initially it was 0). Image
[162/236] The second variable is a uint256, it takes up a complete 32 bytes. As it is taking 32 bytes it would not be able to fit in slot 0, so it is on Slot 1. Let us change the After Dropdown and change to number, it would show the block.timestamp stored in uint256. Image
[163/236] The next variable in the contract is an uint8, that would take 1 byte and the variable is again uint8 which would take 1 byte as well and the next variable is uint16 which would take 2 bytes. The variable after this is bytes32 so slot 2 would have 3 variables. Image
[164/236] In order to decipher the information we need to separate each variable, it starts from right to left.
[165/236] The first 2 hex digits from the right would be "0a" that would be the "flattening" variable. As can be seen it gives 10 when converted to number which matches with initiation in the Contract. Image
[166/236] The next 2 hex digits are "ff" which corresponds to "denomination" variable and as can be seen it converts to 255 matching with the initiation value. Image
[167/236] The next 4 hex digits are "8fb8" which corresponds to awkwardness variable which is equivalent to now keyword that is deprecated.

Notice all these 3 variables are private yet the data was easily accessible. Image
[168/236] Now to the next variable, data it is a bytes32 array of size 3. These would correspond to the next 3 slots.

The key that is required for unlock corresponds to data[2], this refers to the last slot (as array starts from 0). Image
[169/236] The data is stored as bytes32 while the key needs it in bytes16, this means only the left 32 characters after 0x are required.

As can be seen the key is fed into the unlock function, the locked variable state changes to false.

Task done! On to the next challenge! Image
[170/236] LEVEL 13 - GATEKEEPER ONE

Task - Make it pass the gatekeeper and register as an entrant.

Hint - Remember the Telephone and Token level; gasleft (docs.soliditylang.org/en/v0.8.3/unit…) Image
[171/236] The Gatekeeper Contract has 3 modifiers and 1 function. The 3 modifiers are the different "gates" and are all included in the function. Image
[172/236] Modifier gateOne requires that the msg.sender is not equal to tx.origin. This simply means that interaction will be through a smartContract.
[173/236] This is because when interacting directly with an EOA, msg.sender would be equal to tx.origin where as in the case of using a Smart Contract, the msg.sender would be the Contract and tx.origin would be the EOA.
[174/236] Modifier gateTwo requires the gasleft's mod with 8191 is equal to zero.

In other words that when this particular OPCODE is being interacted with, the gasleft should be a multiple of 8191.
[175/236] This is tricky, there are multiple ways of doing this but the approach I used was the call function had the gas specified.
[176/236] Once a random amount of gas specified was used, the Remix degugging tool was used after executing the call and going over the code and observing the gasleft when the code is over gasleft().mod(8191). Image
[177/236] It took a number of trial and errors, it was observed that both numbers are taken and then send to the mod function in the SafeMath library and the numbers sent to the safeMath library is the numbers the code is considering for the comparison. Image
[178/236] Using this, an reducing the amount of gas specified in the call, would get the desired result.

Also need to make sure that the metamask gas limit is matching with the gas specified in the call.
[179/236] Modifier gateThree has 3 require statements. It basically requires a "gatekey" to be input that meets the 3 require statements.

It would be easier to first have a look at the third require statement first.
[180/236] It states that the uint16(tx.origin) should be equal to the uint32(uint64(_gatekey).

The tx.origin would be the EOA. The uint16 of the EOA in hex, would be the last 4 digits.

uint16 is 16 bit; 1 hex digit is 4 bits; hence the 4 digits.
[181/236] The reason that the last 4 digits are being taken as these are the smaller ones. More details about formats (jeancvllr.medium.com/solidity-tutor…)

uint32(uint64(gatekey) == uint16(tx.origin) in other words means 0x00009549 == 0x9549
[182/236] 9549 is the last 4 digits of the EOA, in my case.

Now on to the first require statement which basically has the same requirement as the 3 require statement that uint32 == uint16 as explained previously.
[183/236] The second require statement wants that uint32 != uint64, a simplified solution is to put any random number (on higher order numbers - more than uint32).

I used 0x0000000100009549
[184/236] Instead of the 1 at the 9th digit anything else (besides 0) could have been used and the require statement would pass.

The uint64 is passed of as bytes8.
[185/236] Now on to the Function, enter(), it has the 3 modifiers as above and once is passes through it sets the entrant as tx.origin.

Once this achieved, the task would be completed.
[186/236] In the attack Contract AttackGateKeeperOne(), the address of the Gatekeeper contract is specified, the key is also stored as a variable.

There is 1 function that call the GatekeeperContract's enter function and passes through the key and also specifies the gaslimit. Image
[187/236] So let us deploy the Contract and run the callEnter() function.

It should be noted that to get the gasleft() it did take a number of times to get it right, only the correct solution is shown here.
[188/236] Once the modifiers are passed, the entrant is updated to the player.

The task is completed, on to the next challenge! Image
[189/236] LEVEL 14 - GATEKEEPER TWO

Task - Make it pass the gatekeepers and register as an entrant.

Hint - Remember the GatekeeperOne, assembly and XOR Image
[190/236] The GatekeeperTwo Contract also similar to GatekeeperOne, has 3 modifiers and 1 function. The 3 modifiers are the different "gates" and are all included in the function.
[191/236] Modifier gateOne is the same as an previous challenge. It requires that the msg.sender is not equal to tx.origin. This simply means that interaction will be through a smartContract.
[192/236] This is because when interacting directly with an EOA, msg.sender would be equal to tx.origin where as in the case of using a Smart Contract, the msg.sender would be the Contract and tx.origin would be the EOA.
[193/236] Modifer GateTwo has an assembly that require x is equal to zero. x is extcodesize(caller()).
[194/236] The extcodesize is part of Solidity assembly (docs.soliditylang.org/en/v0.8.13/ass…), it checks whether the a contract has code. It would be checking for the attack contract in this case as it would be the caller.
[195/236] Now the Attacker Contract, needs to do the "attack", yet not have any code.

How can this be done, the answer is there within the responses over here (ethereum.stackexchange.com/questions/1564…)
[196/236] extcodesize does not count the code that is within the constructor. Basically this means that the code that does the call needs to be within the constructor.
[197/236] Modifier GateThree has a require statement that needs the msg.sender address encoded with keccak256 and then the uint64 of its bytes32 with XOR of uint64 of the gatekey needs to be equivalent to (uint64(0) - 1).
[198/236] Complicated! Well on first look yes but it can be simplified greatly.

The right hand side (uint64(0) - 1) is basically an underflow. It will move from 0 to uint64 max.
[199/236] On to the left hand side, let us look at what XOR is. The truth table shows that if either of the inputs are true then output is true but if both or neither of the inputs are true then it gives false. Image
[200/236] So the require statement needs keccak256 of msg.sender which in this case would be Attack Contract and there needs to be computation done based on this and all of it in the constructor!
[201/236] Well none of it is required, let us look at XOR properties and more specifically the inverse of XOR (stackoverflow.com/questions/1427…)

so if a ^ b = c
then b = c ^ a
[202/236] How can we use this to our advantage, The key required would be XOR of uint64 max with the keccak256 of the msg.sender.

This can be seen in the attack contract. Image
[203/236] The enter function has the 3 modifiers and if it passes through the entrant is updated to tx.origin.
[204/236] The attack Contract, AttackGateKeeperTwo, has a Constructor with the key and GatekeeperTwo address stored.

It calls the GateKeeperTwo contract and passes the key.
[205/236] Once this is done successfully, the entrant is now the player.

Task is completed! On to the next one! Image
[206/236] LEVEL 15 - NAUGHT COIN

Task - Transfer all tokens (that are "time locked") to another address

Hint - ERC20 Spec Image
[207/236] The NaughtCoin is an ERC20 Contract. The Constructor sets up the ERC Token Name, Symbol, Supply.

It also mints all the tokens to the player address and emits a Transfer event.
[208/236] The contract also has a transfer function that has a lockTokens modifier.

The lockmodifier checks if the msg.sender is player then now should be greater than timelock.
[209/236] The now keyword has been deprecated (docs.soliditylang.org/en/v0.8.15/070…), block.timestamp should be used instead.

The timelock has been specified as the time of contract deployment + 10 years.
[210/236] The transfer function, has super.transfer within it. The super keyword is to give access to parent Contract in this case ERC20 (more info ethereum.stackexchange.com/questions/1292…).
[211/236] As this is an ERC20 token, there are multiple ways of transferring tokens. Instead of using transfer function, it is possible to give allowance to another address (can be an EOA or a Contract).
[212/236] The address that has been given allowance, can the use the transferFrom function to transfer the tokens from player to any other contract.
[213/236] In the contract, the other functions such as Allowance and TransferFrom is not visible.

In order to view all of these, let us have a look at the Contract ABI. This will show all of these functions. Image
[214/236] In order to make it easier, remix was used. The Instance Contract was copied and pasted into Remix to interact with the Contract.

All of the functions are visible in the side bar. These are all inherited from the ERC20 (eips.ethereum.org/EIPS/eip-20). ImageImageImage
[215/236] In order to move out the tokens, allowance was given to another address that I had access to.

Once allowance has been given, changing the active address and then using the transferFrom function to move the tokens. ImageImage
[216/236] This allowed to change the balance to 0. Thereby meeting the requirement of the task.

On to the next challenge!
[217/236] LEVEL 16 - PRESERVATION

Task - Claim ownership of the Instance Contract

Hint - delegatecall, storage access Image
[218/236] The Preservation Contract has a Constructor that sets the library address and sets the msg.sender as owner.

There are 2 functions, each one is to set the time by calling the individual timezonelibraries.
[219/236] It is done via a delegatecall. This was explained in detail for Level 6. The main point to note here is that the state variables of Caller Contract (Preservation Contract) are read and written.
[220/236] The state variables of Target Contract (Time Zone Library Contract) are not read or written.

Also the storage would be done by the slot number of the Target Contract (medium.com/coinmonks/dele…).
[221/236] There is also a libraryContract that has the function that would be called via the 2 functions above through their respective timeZonelibrary.

A thing to observe in the library is that there is only 1 variable stored that is the uint storedTime.
[222/236] Now back to the Contract. The task of this challenge is to change the owner variable in the Preservation Contract to player.

This is stored in slot 2 (3rd Slot).
[223/236] Now on to the strategy, we need to run the setFirstTime function for TimeZone1Library and input the address as the attack Contract address.
[224/236] The input sent into the function is stored on slot 0. This is because of the way how delegatecall changes the variable of the caller contract.

Now that the address of timeZone1Library is changed to Attack Contract.
[225/236] The attack Contract would have 3 variables stored, and the it would have the same function as in the library.

The function would update the 3rd variable, this would correspond to the address of owner in the Preservation Library. ImageImage
[226/236] So when running the setFirstTime() function a second time it will delegatecall the Attack Contract.

Once the function is run it would update the owner address to player.

This would meet the task requirement! On to the next challenge! ImageImageImage
[227/236] LEVEL 17

LEVEL 17 - RECOVERY

Task - Recover ether from lost Contract address Image
[228/236] The Contract Creator had built a token factory contract for anyone create new tokens.

After deploying the first token contract, the creator sent 0.001 ether to obtain more tokens. They have since lost the contract address.
[229/236] As per the ABI there is only access to generateToken function within the Recovery contract.

This function can be used to create new tokens using Simple Token Contract. Image
[230/236] The Simple Token Contract has a Constructor that sets the name and balance. There is a receive() function, a transfer() function and a destroy() function.
[231/236] The destroy function has selfdestruct in it and the address would be the user input one.

This is perfect for our task, if we can access this function in the lost contract, the ether can be sent back to the player.
[232/236] To decipher the address of the lost contract, let us use the instance contract and check on etherscan.

There are a number of transactions, we are interested in the first one.

This is the one when the lost contract was deployed. ImageImage
[233/236] Over here in the transaction details it can be seen that it has a balance of 0.001 ether.

This is what needs to be recovered. Image
[234/236] In order to access this contract, the source code was pasted into remix and specified that the contract that needs to be interacted has been deployed at the specified address. ImageImage
[235/236] Then the destroy() function was run with the input as the player address. This resulted in selfdestruct of the contract and transfer of the funds back to the player.

Task completed! ImageImage
[236/236] Did you enjoy this thread?!

Would you like a part 2 or some other series ?!

#cryptosecurity #ethereum
Would like to give a shoutout to some who have helped / inspired / motivated / supported me in my web3 journey (in no order!):

Blockchain Security Firms plus Tools:
@trailofbits
@CertoraInc

Blockchain Security Firms

@sherlockdefi
@SpearbitDAO
@CertiK
Individuals:
@PatrickAlphaC
@ProgrammerSmart

Communities / Clubs:
@TheSecureum
@encodeclub

Organization:
@ETHGlobal
@crypton_studio

Bug Bounties:
@immunefi
@code4rena

Plus many more!

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Ikhlas Mohammad

Ikhlas Mohammad Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(