Blockchain CTF write-up: Ethernaut GatekeeperTwo level

Part 2

Raul Riesco
Coinmonks
Published in
6 min readJun 12, 2018

--

Yes, level “Two” is more complex and challenging than level “One” of Ethernaut CTF.

I would recommend you to check GatekeeperOne level write-up before you continue reading this write-up.

This time, I will describe “GatekeeperTwo” 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 GatekeeperTwo, that means to properly & completely execute “enter function”:

GatekeeperTwo source code of delivered instance to hack

Again, as it happened with GatekeeperOne, if you have a look to the code, we would have again, to guess a new “_gateKey” to be passed as parameter to the “enter” function, as well as to hack the new (and different) 3 modifiers (gateOne, gateTwo and gateThree security checks) at the same time.

“enter” function that we have to exploit with its 3 gateX (X=One,Two,Three) modifiers

Once we get that, as it happened with GatekeeperOne smartcontract, 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.

What are the differences from GatekeeperOne in GatekeeperTwo?

The main differences came due to gateTwo and gateThree modifiers.

gateOne is exactly the same as GatekeeperOne’s as you can see:

gateOne modifier code

If you remember, that barrier was quite easy to overcome. We just need to create a malicious smartcontract that will make a remote call to enter function on behalf of my wallet.

Why GatekeeperTwo is more complex than GatekeeperOne level?

It is really more complex than previous GatekeeperOne. Let´s see gateTwo and gateThree in details in order you can understand this statement.

If you see gateTwo:

gateTwo modifier code

Did you notice something strange?

Yes, inline Assembly calls are possible in solidity source code mixed with the rest of the code. Take care in this type of calls as all relevant verification (array lenght checks to avoid out of bounds checks and so on..) will not be considered if you use inline assembly calls.

But, the really strange thing is that if we check the meaning of “extcodesize” condition, that means that the code size of the caller (malicious) smartcontract needs to be zero as a condition!!!!

How can we make it possible?

Our Malicious Smartcontract size will be always above 0 isn´t it?

Is there an exception?: Yes.

How? Code size will be above 0 except when we make all happening (remote “enter” call exploitation) inside the constructor function of our malicious smartcontract! That means, that the malicious smartcontract will make an automatic exploitation while being deployed. After that, it will have extcodesize = real code size.

Ok, then. We have to insert our remote function call (malicious smartcontract calling enter function of instance smartcontract to be hacked) inside the constructor function code logic.

But, what about gateThree security check?

Like gateOne and gateTwo, gateThree will have just one opportunity to be hacked and it is just when malicious smartcontract is being deployed.

Let´s see gateThree modifier’s code:

gateThree modifier code

_gateKey is again, a parameter of “enter” function.

Do you notice something in the condition to be met?

Yes, it is an underflow condition!

uint64(0) — 1

will result in FFFFFFFFFFFFFFFF

So we would have to force an underflow condition in:

uint64(keccak256(msg.sender)) ^uint64(_gateKey)

but here it goes….

msg.sender will be our malicious smartcontract address but it will be dynamically assigned while being deployed….

How can we make a condition like this being met if variables are assigned while deploying and at the same time we have just only one opportunity (one try) to hack it inside the constructor? :)

This is the tricky part, it can also be possible!!

To better understand it, let me show you the malicious smartcontract code that I prepared for this one-time-exploitation, it is slightly different and more complex than GatekeeperOne but if you have read previous case, you would have better understanding of this new code:

Malicious Smartcontract to exploit / hack GatekeeperTwo level

First, we will control dynamic smartcontract assigned address by using “this” variable.

As you can see all the logic is inside the “constructor” function:

function Gatekeeperhack(){ …}

We needed to produce a underflow condition, then, we would calculate “_gateKeyMasked” filtering big endian MSB (8 bytes — 64 bits).

_gateKeyMasked_chunk” represent the interesting piece — just those 8 bytes (bytes8 type).

And here it comes the “magic”: (‘^’ means XOR operation)

If you want to have something like

‘a’ XOR ‘b’ = FFFFF… (underflow) (see gateThree modifier code),

being

‘a’ = keccak256(this);

then

‘b’ would have to be = ‘a_negation’,

where

‘a_negation’ = ‘a’ XOR ‘FFFF…’

I named ‘_gateKey_hashed_negation’, to the variable that will be dynamically calculated based on the negation of ‘a’, which is dependent on msg.sender, that is, dependent on the malicious smartcontract ‘dynamically’ assigned address during deploying process…. :)

If we pass this bytes8 variable as a parameter to the remote call of “enter” function, we would have the XOR operation

‘a’ XOR ‘a_negation’

that will result in “FFF….” equal to uint64(0)-1 which is also “FFF…”

And yes, everything need to happen while deploying the malicious smartcontract, everything dynamically calculated and assigned & called at the same time!!!

It is something like a “one-time-hack” :)

Again, I used REMIX IDE debugging, here I show you some screenshots of different parts of the process (remember that debugging has been done under Javascript VM environment, but exploiting will be done in blockchain ropsten testnet network):

gateTwo-> extcodesize == 0 ! (debug: JUMPDEST)
successful gateThree hack (debug: JUMPDEST instruction)
“entrant” variable re-assigned to our wallet (debug: SSTORE instruction)
“True” returned after successful execution of enter function!

Summarising, you will have to deploy the malicious smartcontract inside Ropsten Test Blockchain Network where the Smartcontract instance to be hacked is; you will then dynamically and automatically exploit/hack it while ropsten tesnet network is deploying it :)

Deploying Malicious Smartcontract while automatic exploitation is taking place

As always, Open Zeppelin will always give you some recommendations when level is complete:

GatekeeperTwo Level complete
GatekeeperTwo Level complete

By using web console (dev tools) you can also check variable status while the instance is still running in the blockchain. Here we check before and after the exploitation that “entrant variable” was successfully updated with the metamask wallet address (tx.origin):

entrant variable in ropsten blockchain network before and after the exploitation!

I hope you have enjoyed and understood differences between GatekeeperOne and GatekeeperTwo levels.

Last but not least, the code of this exploit smartcontract is available in Github.

Good luck and Happy Hacking!

--

--

Raul Riesco
Coinmonks

Disclaimer: Opinions or messages expressed here are solely my own and do not express the views or opinions of my employer