Challenge:
Our ninth challenge is located in /puzzles/puzzle_9.json. Challenge 9 contains the following:
{
"code": "36600310600957FDFD5B343602600814601457FD5B00",
"askForValue": true,
"askForData": true
}
Hint 1:
The corresponding EVM opcodes are:
CALLDATASIZE
PUSH1 03
LT
PUSH1 09
JUMPI
REVERT
REVERT
JUMPDEST
CALLVALUE
CALLDATASIZE
MUL
PUSH1 08
EQ
PUSH1 14
JUMPI
REVERT
JUMPDEST
STOP
Hint 2:
We have 2 new opcodes here: LT and MUL.
LT checks that the value on the top of the stack
is less than the value directly below it on the stack.
It will place the result (1 for true or 0 for false)
on the top of the stack.
MUL multiplies the two values on the top of the stack
and places the result on top of the stack.
What should our calldata and call value be so that
we can pass both comparison checks and jump over all
of the REVERT opcodes?
Hint 3:
Remember, CALLDATASIZE places the size of call data in bytes on the
stack and CALLVALUE places the call value directly on the stack.
Solution:
This one is pretty straightforward. We can see that we need our calldata
to be greater than 3 as we want the LT comparison to be true.
The second check we need to pass is we want our calldata * call value
to be equal to 8.
A solution to this is any 4 bytes of calldata and 2 wei for our call value.
Solution:
calldata: 0xFFFFFFFF call value: 2
Challenge:
Our eighth challenge is located in /puzzles/puzzle_8.json. Challenge 8 contains the following:
{
"code": "36600080373660006000F0600080808080945AF1600014601B57FD5B00",
"askForValue": false,
"askForData": true
}
Hint 1:
The corresponding EVM opcodes are:
CALLDATASIZE
PUSH1 00
DUP1
CALLDATACOPY
CALLDATASIZE
PUSH1 00
PUSH1 00
CREATE
PUSH1 00
DUP1
DUP1
DUP1
DUP1
SWAP5
GAS
CALL
PUSH1 00
EQ
PUSH1 1B
JUMPI
REVERT
JUMPDEST
STOP
Hint 2:
We have 3 new opcodes here: SWAP5, GAS, and CALL.
SWAP5 exchanges the positions of the 1st and 6th items
on the stack.
GAS takes the remaining gas and places it on top of the stack
CALL is the most complicated of the 3. It accepts the
following arguments from the stack as input (from EVM.codes)
1. gas: amount of gas to send to the sub context to execute.
The gas that is not used by the sub context is returned to this one.
2. address: the account which context to execute.
3. value: value in wei to send to the account.
4. argsOffset: byte offset in the memory in bytes, the calldata of the sub context.
5. argsSize: byte size to copy (size of the calldata).
6. retOffset: byte offset in the memory in bytes,
where to store the return data of the sub context.
7. retSize: byte size to copy (size of the return data).
CALL outputs a 0 on the stack if a revert occurred, else 1.
How can we JUMPI over the REVERT
opcode and land on the JUMPDEST?
Hint 3:
Looking at the code, we can tell that we want CALL to return a 0 value.
Using what we learned from the last puzzle, how can we deploy a contract
that will revert when called into?
Hint 4:
Here is what our stack looks like when we hit the CALL opcode:
_______
| G |
|_____|
| A |
|_____|
| 0 |
|_____|
| 0 |
|_____|
| 0 |
|_____|
| 0 |
|_____|
| 0 |
|_____|
(G = gas left)
(A = address of created contract)
We don't have to worry about arguments or return data.
Hint 5:
We did the heavy lifting here in the puzzle 7 when we went over
contract creation code. The key difference here is that instead
of worrying about the size of the runtime byte code, we want to
set the runtime bytecode of the created contract to produce a
REVERT.
Solution:
We are cooking up another contract!
If we want our RETURN opcode to have a single byte of return data,
we want the size value on the stack to be 01. This time, however,
we do care about what is in memory because we are using the
runtime bytecode when we CALL into it. Therefore, we want RETURN
to place a REVERT into the return data. A simple way to do this
is to have RETURN return only the REVERT opcode or 0xFD.
Putting this all together, our creation code can look like this:
PUSH1 FD
PUSH1 00
MSTORE8
PUSH1 01
PUSH1 00
RETURN
Thus an answer to this puzzle is: 0x60FD60005360016000F3.
With this, CREATE will be called with 0x60FD60005360016000F3.
This creation code returns the 0xFD or REVERT opcode. This serves
as the runtime code of the created contract. When the CALL opcode
executes the subcontext of our newly created contract, it will
therefore REVERT and place a 0 on the stack.
This lets us pass our next EQ comparison and JUMPI to our JUMPDEST.
Challenge:
Our seventh challenge is located in /puzzles/puzzle_7.json. Challenge 7 contains the following:
{
"code": "36600080373660006000F03B600114601357FD5B00",
"askForValue": false,
"askForData": true
}
Hint 1:
The corresponding EVM opcodes are:
CALLDATASIZE
PUSH1 00
DUP1
CALLDATACOPY
CALLDATASIZE
PUSH1 00
PUSH1 00
CREATE
EXTCODESIZE
PUSH1 01
EQ
PUSH1 13
JUMPI
REVERT
JUMPDEST
STOP
Hint 2:
CALLDATASIZE takes size of the calldata
and places it on top of the stack.
PUSH1 pushes 00 on to the top of the stack.
DUP1 duplicates the value at the top of the stack
and places the duplicate on top of the original.
CALLDATACOPY takes 3 arguments from the stack in
the following order (from EVM.codes):
1. destOffset: byte offset in the memory where the result will be copied.
2. offset: byte offset in the calldata to copy.
3. size: byte size to copy.
Example:
Stack:
_______
| 0 |
|_____|
| 3 |
|_____|
| 29 |
|_____|
Calldata:
0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Output in memory:
0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000
CALLDATASIZE takes size of the calldata
and places it on top of the stack.
CREATE takes the following 3 inputs off of the stack in order
(from EVM.codes):
1. value: value in wei to send to the new account.
2. offset: byte offset in the memory in bytes, the instructions of the new account.
3. size: byte size to copy (size of the instructions).
and outputs the address of the deployed contract to the top of the stack.
EXTCODESIZE takes an address at the top of the stack as input
and places the size in bytes of the code at that address on the
top of the stack.
PUSH 01 places 01 on the stack and then EQ checks if the value below
01 in the stack is equal to 01.
Finally, JUMPI will take the jump to the JUMPDEST if the equal
comparison is true.
How can we JUMPI over the REVERT
opcode and land on the JUMPDEST?
Hint 3:
CREATE does not expect the runtime byte code to be in memory,
but rather the creation code itself.
Creation code is used to set the new contract’s initial state
and importantly for this challenge, it also returns a copy the
new contract's runtime code.
Therefore, we want to call CREATE on a section in memory that contains
the instructions for returning a single byte of data.
Hint 4:
We cannot control the section of memory that we will be reading from
as that is handled by the other opcodes in the puzzle. Specifically,
the stack will be:
_______
| 0 |
|_____|
| 0 |
|_____|
| CS |
|_____|
(CS == call data size)
In conjunction, whatever we put in for calldata will also be
placed in memory at offset 0. So, the exact instructions we pass
into calldata will be used by CREATE. We want to pass in creation
code that will return a copy of the new contract's runtime code.
We would like the returned runtime code to be 1 byte long.
Hint 5:
The RETURN opcode takes the following two values on the stack as input:
1. offset: byte offset in the memory in bytes, to copy what will be
the return data of this context.
2. size: byte size to copy (size of the return data).
The copied value is placed into the return data. What values on the stack
would make RETURN place a single byte in the return data?
Solution:
This one was a bit trickier than the ones before it.
As mentioned in the hints, we need to have our creation code
return a single byte. What that byte is is not important.
If we want our RETURN opcode to have a single byte of
return data, we want the size value on the stack to be 01.
The offset value does not matter, as we are only concerned about
reading a single byte and we have already initialized our memory
in this puzzle, so even values we haven't explicitly set will be
readable (it will default to 0).
Putting this all together, our creation code can look like this:
PUSH1 01
PUSH1 XX
RETURN
Thus an answer to this puzzle is: 0x600160FFF3.
With this, CREATE will be called with 0x600160FFF3 (creation code that
returns a single byte) in memory, and thanks to the arguments on the stack,
it will use the entire creation code to create a contract at a new address.
This address will be placed on the stack and used by the EXTCODESIZE opcode
which in turn will place a 1 on the stack as that is how many bytes are
returned by the creation code (and therefore the expected runtime bytes)
of the new contract.
This lets us pass our next comparisons and JUMPI to our JUMPDEST.
Voila!
Challenge:
Our fifth challenge is located in /puzzles/puzzle_6.json. Challenge 6 contains the following:
{
"code": "60003556FDFDFDFDFDFD5B00",
"askForValue": false,
"askForData": true
}
Hint 1:
The corresponding EVM opcodes are:
PUSH1 00
CALLDATALOAD
JUMP
REVERT
REVERT
REVERT
REVERT
REVERT
REVERT
JUMPDEST
STOP
Hint 2:
PUSH1 pushes 00 on to the top of the stack.
CALLDATALOAD uses the value on the top of the
stack as a byte offset in the calldata. From
this byte offset, all bytes afterwards are set to 0.
Examples :
Value on top of Stack:
0
Calldata:
0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
CALLDATALOAD output placed on top of stack:
0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
Value on top of Stack:
31
Calldata:
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
CALLDATALOAD output placed on top of stack:
0xFF00000000000000000000000000000000000000000000000000000000000000
How can we JUMP over the 6 REVERT
opcodes and land on the JUMPDEST?
Hint 3:
PUSH1 00 //Offset 0
CALLDATALOAD //Offset 2
JUMP //Offset 3
REVERT //Offset 4
REVERT //Offset 5
REVERT //Offset 6
REVERT //Offset 7
REVERT //Offset 8
REVERT //Offset 9
JUMPDEST //Offset a
STOP //Offset b
Solution:
We can see that the offset of JUMPDEST is 'a' and we
therefore want 'a' to be on the top of the stack
when we reach the JUMP opcode.
To start, PUSH1 places 0 on top of the stack which means
our CALLDATALOAD will use the offset of 0 and will place
all 32 bytes of calldata on top of the stack.
Knowing this, all we need to do is make our calldata 0x0a.
Since we are using 32 bytes, we will send 63 leading zeros:
0x000000000000000000000000000000000000000000000000000000000000000a
Now when we reach JUMP, 0x0a is on top of the stack, and JUMP
performs a valid jump over the REVERT calls to JUMPDEST.
Challenge:
Our fifth challenge is located in /puzzles/puzzle_5.json. Challenge 5 contains the following:
{
"code": "34800261010014600C57FDFD5B00FDFD",
"askForValue": true,
"askForData": false
}
Hint 1:
The corresponding EVM opcodes are:
CALLVALUE
DUP1
MUL
PUSH2 0100
EQ
PUSH1 0C
JUMPI
REVERT
REVERT
JUMPDEST
STOP
REVERT
REVERT
Hint 2:
CALLVALUE takes the value of the current call in wei
and places it on top of the stack.
DUP1 duplicates the value at the top of the stack
and places the duplicate on top of the original.
MUL multiplies the values on the top of the stack
and places the result on top of the stack
PUSH2 places the next two bytes onto the stack.
EQ checks if the top two items on the stack are equal
and places a 1 on the top of the stack if they are
and a 0 if they are not.
PUSH1 places the next byte onto the stack
JUMPI conditionally jumps to the location on the top
of the stack if the second value on the stack is anything
but 0.
How can we JUMPI over the 2 REVERT
opcodes and land on the JUMPDEST?
Hint 3:
Not all of the opcodes are 1 byte this time
Hint 4:
CALLVALUE //Offset 0
DUP1 //Offset 1
MUL //Offset 2
PUSH2 0100 //Offset 3
EQ //Offset 6
PUSH1 0C //Offset 7
JUMPI //Offset 9
REVERT //Offset a
REVERT //Offset b
JUMPDEST //Offset c
STOP //Offset d
REVERT //Offset e
REVERT //Offset f
Solution:
This puzzle is basically checking if the passed in value is
the square root of 0x100 (256 in decimal)
To solve this level, we just need to pass in a value of 16
wei. This way CALLVALUE places 0x10 on the top of the stack.
DUP will then place another 0x10 on top of it in the stack.
MUL will perform 0x10 * 0x10 and push 0x100 onto the top of the stack.
PUSH2 will place 0x100 on top of the stack and EQ will check that the
top two values of the stack are the same and place 1 on top of the stack.
PUSH1 will place 0x0C on the stack which is the offset location of
our JUMPDEST.
Finally, JUMPI performs a valid jump over the REVERT calls to JUMPDEST.