Yet Another EVM Puzzles Writeup
Hello Friend,
This is writeup that contain solutions for Yet Another EVM Puzzles. I am not interested in explaining opcode by opcode, where you can find them in EVM Code in detail. you can able to easily understand the bytecode by looking to the stack written in comments. Hope you like this writeup.
Intial Setup
git clone https://github.com/mattaereal/yet-another-evm-puzzle/
npm install
npx hardhat play
Puzzle 1
ByteCode
3415600736111760225760003560005236600034f080156022573115602357600080fd5b00
00 34 CALLVALUE // [callvalue]
01 15 ISZERO // [iszero(callvalue)]
02 6007 PUSH1 07 // [0x07, iszero(callvalue)]
04 36 CALLDATASIZE // [calldatasize, 0x07, iszero(callvalue)]
05 11 GT // [calldatasize > 0x07, iszero(callvalue)]
06 17 OR // [(calldatasize > 0x07) OR (iszero(callvalue))]
07 6022 PUSH1 22 // [0x22, (calldatasize > 0x07) OR (iszero(callvalue))]
09 57 JUMPI // jump to 0x22 if (calldatasize > 0x07) OR (iszero(callvalue))
if callvalue is 0 or calldatasize greater than 7, execution flow jump to 0x22
then automatically revert.
0A 6000 PUSH1 00 // [0x00]
0C 35 CALLDATALOAD // [calldata]
0D 6000 PUSH1 00 // [0x00, calldata]
0F 52 MSTORE // store calldata into memory
store calldata into memory at 0x00
offset.
10 36 CALLDATASIZE // [calldatasize]
11 6000 PUSH1 00 // [0x00, calldatasize]
13 34 CALLVALUE // [callvalue, 0x00, calldatasize]
14 F0 CREATE // creating a contract with [callvalue, 0x00, calldatasize]
loads calldata which already stored in memory at offset 0x00
and creating contract with callvalue.
15 80 DUP1 // [address]
16 15 ISZERO // [iszero(address)]
17 6022 PUSH1 22 // [0x22, iszero(address)]
19 57 JUMPI // jump to 0x22 if iszero(address)
checking whether if contract created succesfully with the calldata or not. if not it will revert.
1A 31 BALANCE // balance of the creating contract
1B 15 ISZERO // [iszero(balance(address))]
1C 6023 PUSH1 23 // [0x23, iszero(balance(address))]
1E 57 JUMPI // jump to 0x23 if balance(address) is zero
1F 6000 PUSH1 00 // [0x00]
21 80 DUP1 // [0x00, 0x00]
22 FD REVERT // revert
23 5B JUMPDEST // jumpdestination
24 00 STOP // hault execution
stop
opcode will be executed, if balance of contract the was created with calldata and callvalue must be zero.
To solve this puzzles we need create a contract to transfer amount received and that contract bytecode must be withnin 7 bytes.
let create a contract which gone selfdestruct.
push0 // push 0x00 to stack
selfdestruct // self destruct by sending balance to zero address
Solution
Enter the value to send: 1
Enter the calldata: 0x59ff
Puzzle solved!
Puzzle 2
ByteCode
7faaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa60cc60005b90808253601001906001018060201160255750506000516000351814604957600080fd5b00
00 7F AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA PUSH32 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
21 60CC PUSH1 CC // [0xCC, 32 A's]
23 6000 PUSH1 00 // [0x00, 0xCC, 32 A's]
25 5B JUMPDEST // jump dest
26 90 SWAP1 // [0xCC, 0x00, 32 A's]
27 80 DUP1 // [0xCC,0xCC, 0x00, 32 A's]
28 82 DUP3 // [0x00, 0xCC, 0xCC, 0x00, 32 A's]
29 53 MSTORE8 // store 1 byte into memory
mstore8
store 0xCC
at 0x00
in the memory.
2A 6010 PUSH1 10 // [0x10, 0xCC, 0x00, 32 A's]
2C 01 ADD // [0xdc, 0x00, 32 A's]
2D 90 SWAP1 // [0x00, 0xdc, 32 A's]
2E 6001 PUSH1 01 // [0x01, 0x00, 0xdc, 32 A's]
30 01 ADD // [0x01, 0xdc, 32 A's]
31 80 DUP1 // [0x01, 0x01, 0xdc, 32 A's]
32 6020 PUSH1 20 // [0x20, 0x01, 0x01, 0xdc, 32 A's]
34 11 GT // [0x20 > 0x01, 0x01, 0xdc, 32 A's]
35 6025 PUSH1 25 // [0x25, 0x020 > 0x01, 0x01, 0xdc, 32 A's]
37 57 JUMPI // jump to 0x25 if 0x20 > 0x01
the loop from 0x37 to 0x25 will happen 31 (0x1f) times and memory ended up with ccdcecfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfc0c1c2c3c4c5c6c7c8c9cacbc
38 50 POP // [0xbc, 32 A's]
39 50 POP // [32 A's]
3A 6000 PUSH1 00 // [0x00, 32 A's]
3C 51 MLOAD // [data stored in memory, 32 A's]
3D 6000 PUSH1 00 // [0x00, data stored in memory , 32 A's]
3F 35 CALLDATALOAD // [calldata, data stored in memory , 32 A's]
40 18 XOR // [xor(datastored in memory, calldata), 32 A's]
41 14 EQ / [xor(datastored in memory, calldata) == 32 A's]
42 6049 PUSH1 49 // [0x, xor(datastored in memory, calldata) == 32 A's]
44 57 JUMPI // jump to 0x49 if xor(datastored in memory, calldata) == 32 A's
45 6000 PUSH1 00 // [0x00]
47 80 DUP1 // [0x00, 0x00]
48 FD REVERT // revert
49 5B JUMPDEST // jump destination
4A 00 STOP // hault execution
In order to solve this puzzles we need provide with a callvalue where xor of data stored in memory and callvalue must be equal to 32 A’s.
ccdcecfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfc0c1c2c3c4c5c6c7c8c9cacbc // value in memory
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa // 32 A's
val1 = 0xccdcecfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfc0c1c2c3c4c5c6c7c8c9cacbc
val2 = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
callvalue = val1 ^ val2
print(hex(callvalue)) #0x66764656a6b68696e6f6c6d62636061666764656a6b68696e6f6c6d626360616
Solution
Enter the calldata: 0x66764656a6b68696e6f6c6d62636061666764656a6b68696e6f6c6d626360616
Puzzle solved!
Puzzle 3
ByteCode
34156021576000356000523660006000f0600060006000346000945af1156026575b600080fd5b00
00 34 CALLVALUE // [callvalue]
01 15 ISZERO // [iszero(callvalue)]
02 6021 PUSH1 21 // [0x21, iszero(callvalue)]
04 57 JUMPI // jump to 0x21 if callvalue is zero
The contract will revert if the callvalue is zero.
05 6000 PUSH1 00 // [0x00]
07 35 CALLDATALOAD // loads 32 bytes of calldata
08 6000 PUSH1 00 // [0x00, calldata[:32]]
0A 52 MSTORE // mstore
Store first 32 bytes of calldata into memory.
0B 36 CALLDATASIZE // [calldatasize]
0C 6000 PUSH1 00 // [0x00, calldatasize]
0E 6000 PUSH1 00 // [0x00, 0x00, calldatasize]
10 F0 CREATE // [address]
loading calldata from memory and creating contract with that.
11 6000 PUSH1 00 // [0x00, address]
13 6000 PUSH1 00 // [0x00, 0x00, address]
15 6000 PUSH1 00 // [0x00, 0x00, 0x00, address]
17 34 CALLVALUE // [callvalue, 0x00, 0x00, 0x00, address]
18 6000 PUSH1 00 // [0x00, callvalue, 0x00, 0x00, 0x00, address]
1A 94 SWAP5 // [address, 0x00, callvalue, 0x00, 0x00, 0x00, address]
1B 5A GAS // [gas, address, 0x00, callvalue, 0x00, 0x00, 0x00, address]
1C F1 CALL // 1 for succes, 0 for revert
1D 15 ISZERO // [iszero(call success or not)]
1E 6026 PUSH1 26 // [0x26, iszero(call success or not)]
20 57 JUMPI // jump to 0x26 if the call made is revert
21 5B JUMPDEST // jump destination
22 6000 PUSH1 00 // [0x00]
24 80 DUP1 // [0x00, 0x00]
25 FD REVERT // revert
26 5B JUMPDEST // jump destination
27 00 STOP // hault execution
In order to solve this puzzle we need to create a contract that will revert.
runtime code to revert
push0
push0
revert
// runtime bytecode: 5f5ffd
contract creation code which return the runtime code
push3 0x5f5ffd
push0
mstore // store runtime code in memory
push1 0x03 // size
push1 0x1d // offset 0x20-0x03
return // return runtime code
// contract creation code: 0x625f5ffd5f526003601df3
Solution
Enter the value to send: 1
Enter the calldata: 0x625f5ffd5f526003601df3
Puzzle 4
ByteCode
60203611602157366000366020033736806020032060e01c63890d6908146026575b600080fd5b00
00 6020 PUSH1 20 // [0x20]
02 36 CALLDATASIZE // [calldatasize , 0x20]
03 11 GT // [calldatasize > 0x20]
04 6021 PUSH1 21 // [0x21, calldatasize > 0x20]
06 57 JUMPI // jump to 0x21 if calldatasize greater than 32 bytes
The contract revert is the calldatasize is greater than 32 bytes.
07 36 CALLDATASIZE // [calldatasize]
08 6000 PUSH1 00 // [0x00, calldatasize]
0A 36 CALLDATASIZE // [calldatasize, 0x00, calldatasize]
0B 6020 PUSH1 20 // [0x20, calldatasize, 0x00, calldatasize]
0D 03 SUB // [0x20 - calldatasize, 0x00, calldatasize]
0E 37 CALLDATACOPY // copy calldata into memory
the above code store calldata into memory at offset 0x00
.
0F 36 CALLDATASIZE // [calldatasize]
10 80 DUP1 // [calldatasize, calldatasize]
11 6020 PUSH1 20 // [0x20, calldatasize, calldatasize]
13 03 SUB // [0x20 - calldatasize, calldatasize]
14 20 SHA3 // compute keccakhash of calldata
the above code computing the keccak256 hash of the calldata.
15 60E0 PUSH1 E0 // [0xe0, keccak256(calldata)]
17 1C SHR // keccak256(calldata)[:4]
18 63890D6908 PUSH4 890D6908 // [0x890D6908 , keccak256(calldata)[:4]]
1D 14 EQ // [0x890D6908 == keccak256(calldata)[:4]]
1E 6026 PUSH1 26 // [0x26, 0x890D6908 == keccak256(calldata)[:4]]
20 57 JUMPI // jump to 0x26 if 0x890D6908 == keccak256(calldata)[:4]
21 5B JUMPDEST // jump destination
22 6000 PUSH1 00 // [0x00, 0x00]
24 80 DUP1 // [0x00]
25 FD REVERT // revert
26 5B JUMPDEST // jump destination
27 00 STOP // hault execution
we need to find a function whose signautre is 0x890d6908
.
just gone to 4byte Ethereum Signature Database and searched for 0x890d6908
.
Found that function signature of 0x890d6908
is solve()
.
abi.encodePacked("solve()") // 0x736f6c76652829
Solution
Enter the calldata: 0x736f6c76652829
Puzzle solved!
Puzzle 5
ByteCode
6004361160385760003560e01c6000600034f57f00000000000000000000000034d5cbd8a2b5e1bb6952581ae65b47ed2bd5ef2d14603d575b600080fd5b00
00 6004 PUSH1 04 // [0x04]
02 36 CALLDATASIZE // [calldatasize, 0x04]
03 11 GT // [calldatasize > 0x04]
04 6038 PUSH1 38 // [0x38, calldatasize > 0x04]
06 57 JUMPI // jump to 0x38 if calldatasize > 0x04
The contract will revert if the calldatasize greater than 4
.
07 6000 PUSH1 00 // [0x00]
09 35 CALLDATALOAD // [calldata]
0A 60E0 PUSH1 E0 // [0xe0, calldata]
0C 1C SHR // [calldata[:4]]
0D 6000 PUSH1 00 // [0x00]
0F 6000 PUSH1 00 // [0x00, 0x00, calldata[:4]]
11 34 CALLVALUE // [callvalue, 0x00, 0x00, calldata[:4]]
12 F5 CREATE2 // create contract
creating contract with the given value.
13 7F 00000000000000000000000034D5CBD8A2B5E1BB6952581AE65B47ED2BD5EF2D PUSH32 00000000000000000000000034D5CBD8A2B5E1BB6952581AE65B47ED2BD5EF2D
34 14 EQ // [push32ed value == created contract address]]
35 603D PUSH1 3D //[0x3d, push32ed value == created contract address]
37 57 JUMPI // jump to 0x3d if push32ed value == created contract address
38 5B JUMPDEST // jump destination
39 6000 PUSH1 00 // [0x00]
3B 80 DUP1 // [0x00, 0x00]
3C FD REVERT // revert
3D 5B JUMPDEST // jumpt destination
3E 00 STOP // hault destination
we have to give a calldatasize with 4 bytes, contract created with given calldata is equal to 00000000000000000000000034D5CBD8A2B5E1BB6952581AE65B47ED2BD5EF2D
I am not interested in bruteforce attack with my delicated laptop.
so i search for writeup over internet and found from notonlyowner.
answer is 0xdeadbeef but only works in evm.code’s playground for first time only.
Solution
? Enter the calldata: 0xdeadbeef
Puzzle 6
ByteCode
346001116014573060003560601c1832146019575b600080fd5b00
00 34 CALLVALUE // [callvalue]
01 6001 PUSH1 01 // [0x01, callvalue]
03 11 GT // [0x01 > callvalue]
04 6014 PUSH1 14 // [0x14, 0x01 > callvalue]
06 57 JUMPI // jump to 0x14 if callvalue less than 1
The contract will revert if the callvalue is less than 1.
07 30 ADDRESS // [address(this)]
08 6000 PUSH1 00 // [0x00, address(this)]
0A 35 CALLDATALOAD // [calldata, address(this)]
0B 6060 PUSH1 60 // [0x60, calldata, address(this)]
0D 1C SHR // [calldata[12:], address(this)]
0E 18 XOR // [xor(calldata[12:], address(this))]
0F 32 ORIGIN // [origin, xor(calldata[12:], address(this))]
10 14 EQ // [origin == xor(calldata[12:], address(this))]
11 6019 PUSH1 19 // [0x19, origin == xor(calldata[12:], address(this))]
13 57 JUMPI // jump to 0x19 if origin == xor(calldata[12:], address(this))
14 5B JUMPDEST // jump destination
15 6000 PUSH1 00 // [0x00]
17 80 DUP1 // [0x00, 0x00]
18 FD REVERT // revert
19 5B JUMPDEST // jump destination
1A 00 STOP // hault execution
the ultimate way to solve the puzzle to find a address such that xor(address, calldata) == origin
.
below values in python code are taken from evm.code’s playground.
address = 0x9bbfed6889322e016e0a02ee459d306fc19545d8
origin = 0xbe862ad9abfe6f22bcb087716c7d89a26051f74c
solution = address ^ origin
print(hex(solution)) // 0x2539c7b122cc4123d2ba859f29e0b9cda1c4b294
Solution
Enter the value to send: 1
Enter the calldata: 0x2539c7b122cc4123d2ba859f29e0b9cda1c4b294
Puzzle solved!
Puzzle 7
ByteCode
600160003504600190037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14603057fd5b00
00 6001 PUSH1 01 // [0x01]
02 6000 PUSH1 00 // [0x00, 0x01]
04 35 CALLDATALOAD // [calldata, 0x01]
05 04 DIV // [calldata]
06 6001 PUSH1 01 // [0x01, calldata]
08 90 SWAP1 // [calldata, 0x01]
09 03 SUB // [calldata - 0x01]
above code, subtracting 1 from calldata.
0A 7F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH32 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
2B 14 EQ // [2**256 == calldata - 0x01]
2C 6030 PUSH1 30 // [0x30, 2**256 == calldata - 0x01]
2E 57 JUMPI // jump to 0x30 if 2**256 == calldata - 0x01
2F FD REVERT // revert
30 5B JUMPDEST // jump destination
31 00 STOP // hault execution
In order to solve this puzzle, we need to find a callvalue the wil leaves 2**256
when subtracted by 1.
callvalue - 1 == 2**256
=> callvalue = 0
we know 0 - 1 == 2**256
, underflow
Solution
Enter the calldata: 0x00
Puzzle solved!
Puzzle 8
ByteCode
600060005260003560081b60f81c6001015b600f6000510160005280156026573490036011565b600051905060e114603657600080fd5b00
00 6000 PUSH1 00 ----
02 6000 PUSH1 00 |
04 52 MSTORE |
05 6000 PUSH1 00 |
07 35 CALLDATALOAD |
08 6008 PUSH1 08 |
0A 1B SHL |
0B 60F8 PUSH1 F8 |
0D 1C SHR ----
0E 6001 PUSH1 01 | 2nd byte of calldata + 1
10 01 ADD |
11 5B JUMPDEST ------------
12 600F PUSH1 0F | |
14 6000 PUSH1 00 | | add 0x0f + 32 byte of memory
16 51 MLOAD | |
17 01 ADD | | and store it to memory
18 6000 PUSH1 00 | |
1A 52 MSTORE ------------- |
1B 80 DUP1 |
1C 15 ISZERO |
1D 6026 PUSH1 26 --------| |
1F 57 JUMPI | |
20 34 CALLVALUE | |
21 90 SWAP1 | |
22 03 SUB | |
23 6011 PUSH1 11 -------------------
25 56 JUMP
26 5B JUMPDEST
27 6000 PUSH1 00 |
29 51 MLOAD | value in the memory should be 0xe1 (255)
2A 90 SWAP1 |
2B 50 POP
2C 60E1 PUSH1 E1
2E 14 EQ
2F 6036 PUSH1 36
31 57 JUMPI
32 6000 PUSH1 00
34 80 DUP1
35 FD REVERT
36 5B JUMPDEST
37 00 STOP
From 00
to 04
, the bytecode fill the 32 bytes of memory with zero’s.
From 05
to 0D
, calldata is loaded into stack and shifted 1 byte to left, 31 bit data with 1 byte zero, then that will be right shifted with 31 bytes then the stack will only contain 2nd byte
of the calldata.
For each iteration b/w 11
and 23
value in the memory is increased with 0x0f
(15). to get that value to be 0xe1
(225), we need to iterate over 15 times.
For each iteration the (2nd byte of calldata+1) is decreased by callvalue, at where the calldata-callvalue = 0, that must be after 15 iterations.
after the difference of calldata-callvalue = 0, also 1 more addition of 0x0f takes place, so we have to calculate to decrease 14 times.
let us take callvalue as 1.
callvalue = 1
calldata = 0
for i in range(14):
calldata = calldata + callvalue
print(calldata - 1) // 2nd byte of calldata + 1 is decreased
Solution
Enter the value to send: 1
Enter the calldata: 0x000d
Puzzle solved!
Thank you for reading!