Puzzle 8

Puzzle

############
# Puzzle 8 #
############

00      34        CALLVALUE
01      15        ISZERO
02      19        NOT
03      6007      PUSH1 07
05      57        JUMPI
06      FD        REVERT
07      5B        JUMPDEST
08      36        CALLDATASIZE
09      6000      PUSH1 00
0B      6000      PUSH1 00
0D      37        CALLDATACOPY
0E      36        CALLDATASIZE
0F      6000      PUSH1 00
11      6000      PUSH1 00
13      F0        CREATE
14      47        SELFBALANCE
15      6000      PUSH1 00
17      6000      PUSH1 00
19      6000      PUSH1 00
1B      6000      PUSH1 00
1D      47        SELFBALANCE
1E      86        DUP7
1F      5A        GAS
20      F1        CALL
21      6001      PUSH1 01
23      14        EQ
24      6028      PUSH1 28
26      57        JUMPI
27      FD        REVERT
28      5B        JUMPDEST
29      47        SELFBALANCE
2A      14        EQ
2B      602F      PUSH1 2F
2D      57        JUMPI
2E      FD        REVERT
2F      5B        JUMPDEST
30      00        STOP

? Enter the calldata: 

Solution

NOT may behave differently than what you think: it flips every single byte in its input. In high-level languages, the NOT operation turns 0 to 1 and 1 to 0. This is different from what we have here.

Chunk 1

00      34        CALLVALUE
01      15        ISZERO
02      19        NOT
03      6007      PUSH1 07
05      57        JUMPI
06      FD        REVERT
07      5B        JUMPDEST

msg.value can not be 0.

Chunk 2

08      36        CALLDATASIZE
09      6000      PUSH1 00
0B      6000      PUSH1 00
0D      37        CALLDATACOPY
0E      36        CALLDATASIZE
0F      6000      PUSH1 00
11      6000      PUSH1 00
13      F0        CREATE
14      47        SELFBALANCE
15      6000      PUSH1 00
17      6000      PUSH1 00
19      6000      PUSH1 00
1B      6000      PUSH1 00
1D      47        SELFBALANCE
1E      86        DUP7
1F      5A        GAS
20      F1        CALL
21      6001      PUSH1 01
23      14        EQ
24      6028      PUSH1 28
26      57        JUMPI
27      FD        REVERT
28      5B        JUMPDEST

Pseudocode:

calldatacopy(0, 0, calldata_size);
new_address = create(0, 0, calldata_size);
call_success = call(gas, new_address, address(this).balance, 0, 0, 0, 0);

if (call_success) {
    jump(0x28);
}

Chunk 3

29      47        SELFBALANCE
2A      14        EQ
2B      602F      PUSH1 2F
2D      57        JUMPI
2E      FD        REVERT
2F      5B        JUMPDEST
30      00        STOP

It requires the new balance equals to the old balance.

Building calldata

The new contract should not keep any balance when CALL sends balance to it. My idea is to let the contract selfdestruct and send the balance back to the caller.

Runtime code:

PUSH1 0x33 // opcode for CALLER
PUSH1 0x20
MSTORE8

PUSH1 0xFF // opcode for SELFDESTRUCT
PUSH1 0x00
MSTORE8

PUSH1 0x40
PUSH1 0x00
RETURN

Basically this contract only executes selfdestruct(caller) to send the balance back to the challenge contract.

Compile:

603360205360ff60005360406000f3

Creation code:

PUSH15 0x603360205360ff60005360406000f3 // runtime code
PUSH1 0x00
MSTORE

PUSH1 0x0F
PUSH1 0x11
RETURN

Compile:

6e603360205360ff60005360406000f3600052600f6011f3

Last updated