Blockchain CTF write-up: Ethernaut GatekeeperOne level
As you may probably know, Open Zeppelin launched a Blockchain CTF named Ethernaut which is really amazing. There are different levels with different complexity, but I enjoyed all of them. It is also an open source initiative which is a plus.
I decided to write up some of the levels that are still not documented in order to help more people being introduced in blockchain security or at least can have a better idea of some building blocks of blockchain technology.
This time, I will describe “GatekeeperOne” level.
In this level, you are given a blockchain smartcontract instance together with its code written in solidity.
The objective it is to “enter” the GatekeeperOne, that means to properly & completely execute “enter function”:
After a quick look at the code, we would have to guess the “_gateKey” to be passed as parameter to the function, as well as to hack the 3 modifiers (gateOne, gateTwo and gateThree security checks) at the same time. Once we get that, the enter function will simply assign tx.origin (our wallet) as the entrant but it will only happen if all conditions are met in the same call.
gateOne modifier is very simple to hack, we just need to create a malicious smartcontract to call GatekeeperOne smartcontract on behalf, in that case, msg.sender will be our malicious smartcontract address and tx.origin will be our metamask wallet address.
gateTwo modifier is more complex to hack. In this case, the modifier’s code require that “gas left” in a specific moment of the stack execution where the condition is evaluated, msg.gas has to be exactly an integer multiple of “8191” (% means “mod”).
For that, I decided to start debugging from Javascript VM environment in order to check the exact amount of “gas” that it was needed to send in the blockchain transaction in order to fulfill such a condition in the exact point of the stack execution.
While debugging, you will see remaining gas in “step detail” section. I used REMIX IDE as a powerful IDE tool / environment.
As a result, the exact amount of “gas” the smartcontract need to be passed, it was 32979 in the call.
gateThree modifier needs a parameter named “_gateKey” that we have to guess. As all conditions have to be met at the same time, we see interesting logic inside the function.
In last line of code, _gateKey is dependant on tx.origin, that means, that you will have a different _gateKey than I did. I will anyway explain the process that you should do but remember, you will have a different _gateKey as it is dependent on tx.origin (the origin of the transaction, your wallet).
Remember that msg.sender will be the malicious smartcontract address however tx.origin will be your (metamask) wallet.
First, check what is your (metamask) wallet address.
Second, if uint32(_gateKey) has to be equal to uint16(tx.origin) and at the same time, uint32(_gateKey) has to be equal to uint16(_gateKey) that means that you have to mask your wallet address (tx.origin) up to 32 bits (by padding 0's).
This can be done by a simple AND
‘a & b’
operation where “a” is your “tx.origin” address and “b” is a mask like
‘bytes8 public mask = 0xFFFFFFFF0000FFFF’
You can also check uint32, uint 16 and any other alternative operations by using online converters like scadacore.com. Please take care that you will have to work with “big endian” values.
Third, due to the fact that the third condition is that uint32(_gateKey) has to be different (!=) from uint64(_gateKey), we have only padded up to 32 bits (see 0’s in the mask above), the rest, will leave tx.origin bits as they were.
Summarising, I created the above malicious smartcontract named “Gatekeeperhack” as a proxy contract to call the instanced GatekeeperOne on behalf of me.
The variable “target” is linked to the real instance by open zeppelin in Ropsten blockchain testnet (you will have to replace ‘COPY YOUR ETHERNAUT INSTANCE ADDRESS HERE” by your ropsten ethernaut instance address).
The mask provided is completely useful for you.
I used low level “call” to have better control of gas passed (remember gateTwo above), do not forget, that in that case a remote call of a remote function contract, will force you to use :
‘bytes4(sha3(“function_name(parameter_type)”)), parameter_value’
Note: as sha3 is deprecated, it is recommended you use keccak256 instead.
Last but not least, you will receive some recomendations when level is done!
I hope you enjoy this level as I did.
Last but not least the code is available at Github.
Good luck and Happy hacking!