Challenge 1:
The puzzles for these challenges are located in puzzles/
. Challenge 1 contains the following:
{
"code": "36340A56FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE5B58360156FEFE5B00",
"askForValue": true,
"askForData": true
}
Opcodes:
00 36 CALLDATASIZE
01 34 CALLVALUE
02 0A EXP
03 56 JUMP
04 FE INVALID
05 FE INVALID
06 FE INVALID
07 FE INVALID
08 FE INVALID
09 FE INVALID
0A FE INVALID
0B FE INVALID
0C FE INVALID
0D FE INVALID
0E FE INVALID
0F FE INVALID
10 FE INVALID
11 FE INVALID
12 FE INVALID
13 FE INVALID
14 FE INVALID
15 FE INVALID
16 FE INVALID
17 FE INVALID
18 FE INVALID
19 FE INVALID
1A FE INVALID
1B FE INVALID
1C FE INVALID
1D FE INVALID
1E FE INVALID
1F FE INVALID
20 FE INVALID
21 FE INVALID
22 FE INVALID
23 FE INVALID
24 FE INVALID
25 FE INVALID
26 FE INVALID
27 FE INVALID
28 FE INVALID
29 FE INVALID
2A FE INVALID
2B FE INVALID
2C FE INVALID
2D FE INVALID
2E FE INVALID
2F FE INVALID
30 FE INVALID
31 FE INVALID
32 FE INVALID
33 FE INVALID
34 FE INVALID
35 FE INVALID
36 FE INVALID
37 FE INVALID
38 FE INVALID
39 FE INVALID
3A FE INVALID
3B FE INVALID
3C FE INVALID
3D FE INVALID
3E FE INVALID
3F FE INVALID
40 5B JUMPDEST
41 58 PC
42 36 CALLDATASIZE
43 01 ADD
44 56 JUMP
45 FE INVALID
46 FE INVALID
47 5B JUMPDEST
48 00 STOP
Solution:
We are expected to send in both calldata and callvalue for this challenge. Reviewing the opcodes, we can tell that we will be jumping to the location calculated by (CALLVALUE ** CALLDATASIZE). Ideally we’d end up at the final JUMPDEST at 0x47 and call it a day but that’s 71 in decimal and not a particularly friendly number to work with. So, we now have a few options for getting ourselves to the JUMPDEST at 0x40 which is 64 in decimal. Looking ahead, we see that we will be effectively pushing 0x41 onto the stack and then adding the size of our calldata to it before jumping to that location. Cool, we know we want to get to 0x47 so our calldata should be 6 bytes in size. That means our call value will be 2 wei since 2**6 = 0x40.
{"value":2,"data":"0xabcdef012345"}
Challenge 2:
The puzzles for these challenges are located in puzzles/
. Challenge 2 contains the following:
{
"code": "3660006000373660006000F0600080808080945AF13D600a14601F57FEFEFE5B00",
"askForValue": false,
"askForData": true
}
Opcodes:
00 36 CALLDATASIZE
01 6000 PUSH1 00
03 6000 PUSH1 00
05 37 CALLDATACOPY
06 36 CALLDATASIZE
07 6000 PUSH1 00
09 6000 PUSH1 00
0B F0 CREATE
0C 6000 PUSH1 00
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 80 DUP1
12 94 SWAP5
13 5A GAS
14 F1 CALL
15 3D RETURNDATASIZE
16 600A PUSH1 0A
18 14 EQ
19 601F PUSH1 1F
1B 57 JUMPI
1C FE INVALID
1D FE INVALID
1E FE INVALID
1F 5B JUMPDEST
20 00 STOP
Solution:
Let’s break this challenge down into two blocks
Block 1
00 36 CALLDATASIZE
01 6000 PUSH1 00
03 6000 PUSH1 00
05 37 CALLDATACOPY
06 36 CALLDATASIZE
07 6000 PUSH1 00
09 6000 PUSH1 00
0B F0 CREATE
This block is all used to setup the CREATE opcode. All we are doing here is taking our callvalue and copying that into memory to serve as our initializiation code for the new contract we will CREATE.
Block 2
0C 6000 PUSH1 00
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 80 DUP1
12 94 SWAP5
13 5A GAS
14 F1 CALL
15 3D RETURNDATASIZE
16 600A PUSH1 0A
18 14 EQ
19 601F PUSH1 1F
1B 57 JUMPI
This block now makes a call to our newly created contract. Note that all of the arguments are 0 except for gas and the address. The call to the newly address will therefore contain no calldata. After the call, we get back the size in bytes of our return data and check that it is equal to 0x0a. If so, then we will take the JUMPI and complete this challenge. What we want then is to create a contract such that when an external call is made to our contract with no function data it returns data that is 10 (0x0a) bytes long. Conveniently, this can be achieved through a fallback function.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
contract ReturnTenBytes {
fallback(bytes calldata) external returns(bytes memory) {
bytes10 derp = 0x01020304050607080910;
return abi.encodePacked(derp);
}
}
{"data":"0x608060405234801561001057600080fd5b5060eb8061001f6000396000f3fe6080604052348015600f57600080fd5b5060003660606000690102030405060708091060b01b90508060405160200160369190609c565b604051602081830303815290604052915050915050805190602001f35b60007fffffffffffffffffffff0000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60966092826053565b607f565b82525050565b600060a682846089565b600a820191508190509291505056fea2646970667358221220e25716522e5bb854bfb1b889755e9b80453278e42970c53115815194240a401d64736f6c63430008100033","value":0}
Challenge 3:
The puzzles for these challenges are located in puzzles/
. Challenge 3 contains the following:
{
"code": "3660006000373660006000F06000808080935AF460055460aa14601e57fe5b00",
"askForValue": false,
"askForData": true
}
Opcodes:
00 36 CALLDATASIZE
01 6000 PUSH1 00
03 6000 PUSH1 00
05 37 CALLDATACOPY
06 36 CALLDATASIZE
07 6000 PUSH1 00
09 6000 PUSH1 00
0B F0 CREATE
0C 6000 PUSH1 00
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 93 SWAP4
12 5A GAS
13 F4 DELEGATECALL
14 6005 PUSH1 05
16 54 SLOAD
17 60AA PUSH1 AA
19 14 EQ
1A 601E PUSH1 1E
1C 57 JUMPI
1D FE INVALID
1E 5B JUMPDEST
1F 00 STOP
Solution:
This one is a lot like challenge 2 with two minor differences. First, we are now using a DELEGTECALL as opposed to a CALL. Second, instead of checking the size of our return data, we are checking the contents of our fifth storage slot. This is a useful combination as DELEGATECALL allows the call to our newly created contract to modify the caller’s state. Thus we can solve this challenge by setting the fifth storage slot to 0xAA when our contract is called into.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
contract StoreSlotFive {
uint256 slotZero = 0;
uint256 slotOne = 1;
uint256 slotTwo = 2;
uint256 slotThree = 3;
uint256 slotFour = 4;
uint256 slotFive = 5;
fallback() external {
slotFive = 170;
}
}
{"data":"0x6080604052600080556001805560028055600380556004805560058055348015602757600080fd5b50604f8060356000396000f3fe6080604052348015600f57600080fd5b5060aa600581905500fea2646970667358221220dc6332a94c366877000ab52293ddc44fabb9baedc61e97b7ed4fd539d46f21cb64736f6c63430008100033","value":0}
Challenge 4:
The puzzles for these challenges are located in puzzles/
. Challenge 4 contains the following:
{
"code": "30313660006000373660003031F0319004600214601857FD5B00",
"askForValue": true,
"askForData": true
}
Opcodes:
00 30 ADDRESS
01 31 BALANCE
02 36 CALLDATASIZE
03 6000 PUSH1 00
05 6000 PUSH1 00
07 37 CALLDATACOPY
08 36 CALLDATASIZE
09 6000 PUSH1 00
0B 30 ADDRESS
0C 31 BALANCE
0D F0 CREATE
0E 31 BALANCE
0F 90 SWAP1
10 04 DIV
11 6002 PUSH1 02
13 14 EQ
14 6018 PUSH1 18
16 57 JUMPI
17 FD REVERT
18 5B JUMPDEST
19 00 STOP
Solution:
The crux of this challenge is understanding that the CREATE opcode runs the initialization code on the newly created contract. Further, we know that initialization code always contains a contract’s constructor. Therefore, we can create a contract such that the constructor performs our desired actions. But what are the desired actions?
We can see that the balance of the calling context is taken and sent to the CREATE opcode. In this case that will be the call value we are sending along. We can also see that upon successful contract creation, the balance of the new address is checked and then becomes the divisor in a DIV operation with the initial balance. This needs to equal to 2 for us to solve the puzzle. So, we get to choose any values X and Y such that X / Y = 2. X will be our callvalue and Y will be our ending balance for the newly created contract. I chose to send 4 wei to the new contract and to forward 2 of the wei to the 0 address during construction.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
contract LowerBalanceIn {
constructor() payable {
payable(address(0)).send(2);
}
}
{"value":4 "data":"0x6080604052600073ffffffffffffffffffffffffffffffffffffffff166108fc60029081150290604051600060405180830381858888f1935050505050603f8060496000396000f3fe6080604052600080fdfea2646970667358221220d67c79b5559a559287188a32474490c251553397b7209a654a3611ac156a20ef64736f6c63430008090033"}
Challenge 5:
The puzzles for these challenges are located in puzzles/
. Challenge 5 contains the following:
{
"code": "60203611600857FD5B366000600037365903600314601957FD5B00",
"askForValue": false,
"askForData": true
}
Opcodes:
00 6020 PUSH1 20
02 36 CALLDATASIZE
03 11 GT
04 6008 PUSH1 08
06 57 JUMPI
07 FD REVERT
08 5B JUMPDEST
09 36 CALLDATASIZE
0A 6000 PUSH1 00
0C 6000 PUSH1 00
0E 37 CALLDATACOPY
0F 36 CALLDATASIZE
10 59 MSIZE
11 03 SUB
12 6003 PUSH1 03
14 14 EQ
15 6019 PUSH1 19
17 57 JUMPI
18 FD REVERT
19 5B JUMPDEST
1A 00 STOP
Solution:
This challenge doesn’t require creating a contract and is quite straightforward. We need to supply calldata greater in length than 0x20 to jump over the first REVERT
instruction. Then, we need our calldata to be 3 bytes less in size than what is in memory. Huh, but aren’t we placing the calldata in memory? The key here is that MSIZE
reads memory in 0x20 byte increments. So we just need to place in any calldata that is 3 less than a multiple of 0x20 (and greater than 0x20). We can choose 0x40 for the multiple and submit calldata of length 0x3d.
{"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","value":0}